How much Java should you know for a Selenium testing job?

odd-one-out[1]

Selenium tester hiding in the middle of Selenium developers

This is a bad question.

The following are testing jobs:

  • manual testing job
  • functional testing job
  • explorary testing job

 

Since when Selenium has anything in common with manual or functional testing?

Web automation with Selenium WebDriver is not a testing job.

 

Selenium tester does not explain anything about what the job is about.

It is misleading as well since no testing is involved.

When doing web automation with Selenium, you do not

  • create test cases
  • execute test cases
  • explore applications
  • report bugs for your testing
  • map test cases to requirements

 

Instead, you take a user story and implement automated tests for it using Java code and the Selenium WebDriver library.

I said library and not tool or application.

We will come back to the distinction between the library and application.

 

So, there is no such thing as a Selenium testing job.

Test automation with Selenium is a development job.

How much Java should you know for a Selenium development job?

First, you need to be a developer.

 

How much Java should a developer know to do a development job?

In-depth language knowledge (Java, C#, etc).

 

What other skills should the developer have?

Design patterns.

How to create a framework.

How to use an existing framework.

How to document code.

How to use a source control system (GIT, SVN).

How to review code.

How to integrate his code with other developers’ code.

How to use Maven to manage dependencies.

How to use Jenkins for continuous integration.

How to use JUNIT or TESTNG for unit testing.

 

How much Java should a developer know for a Selenium job?

All of the above.

 

But, but, I want to be a Selenium tester.

This cannot be right.

I want to be a tester that uses Selenium.

Tell me what to do.

 

Selenium WebDriver automation is for developers and testers who want to become developers.

Anyone else is just wasting his time with Selenium.

 

One more thing should be said about Selenium WebDriver.

Selenium WebDriver is not an automation tool.

It is not like QTP.
It does not have an IDE.
It does not allow record-and-playback.
It does not create an object repository.

It is instead an automation library.

You cannot use it as is.

You need a lot of other knowledge before you can use.
Programming knowledge.

 

This may be why people talk about Selenium testers.

If Selenium would be a tool, testers should be able to learn it.

They can learn QTP, they can learn SOAP UI.

They should be able to learn Selenium.

But Selenium is not a tool.

It is an automation library.

 


 

Want to become a Selenium developer instead of staying a tester?

I have the right book for you:

Improve Selenium Code with Automation Patterns: Page Object Model Page Factory Page Elements Base Page Loadable Component

 

amazon book

How do you recognize a fake Selenium tester? By his code

recognize trees by their fruits

You can recognize a fake Selenium tester by looking at his code.
Look at

  1. how much code he writes in a day
  2. more importantly, look at how good the code is

 

The following code is written by a fake Selenium tester.

 

I have added some comments to explain what the code does.

 

public void sendKeys(String keyword){ 
  //create a web driver wait with a 4 seconds timeout 
  wait = new WebDriverWait(driver, 4);
  try { 
       //wait until the element is visible 
       wait.until(
            ExpectedConditions.visibilityOfElementLocated
                (elementLocator)); 

       //find the element and type the keyword in it 
       driver.findElement(elementLocator).sendKeys(keyword); 
      
       //display information to console about the typed keyword
       System.out.println("Entered keyword: "+ keyword); 
       
       //wait 1 second 
       waitFor(1000); 
  } 
  //if there is an exception while typing the keyword
  catch(RuntimeException e) { 
    //display info to console about trying again
    System.out.println("\ntry again to type keyword:" + keyword); 

    //wait 2 seconds; see below the wait() method 
    wait(); 

    //find the element again and type the keyword
    driver.findElement(elementLocator).sendKeys(keyword); 

    //find the element one more time and type enter 
    sendKeyEnter(); 
  } 
}

public void sendKeyEnter(){ 
  wait.until(
         ExpectedConditions.visibilityOfElementLocated
              (elementLocator)); 
  driver.findElement(elementLocator).sendKeys(Keys.RETURN);
} 

public void wait(){ 
  try { Thread.sleep(2000); 
  } 
  catch (InterruptedException e) { 
     // TODO Auto-generated catch block 
     e.printStackTrace(); 
  }
} 

public void waitFor(int time){ 
  try { 
    Thread.sleep(time); 
  } 
  catch (InterruptedException e) { 
    // TODO Auto-generated catch block 
    e.printStackTrace(); 
  }
}

 

Not too bad, you would say.

The code is probably created with the intent of

  • trying to type the text in an element that is displayed slowly
  • if the typing fails because the element is not visible yet, wait a few seconds and try again

 

Lets have a second look:

public void sendKeys(String keyword){
   /* 
      why use such a low timeout?  
      if the page is very slow, 4 seconds will not be sufficient; 
      why is the wait object not declared in the sendKeys() method?  
   */ 
   wait = new WebDriverWait(driver, 4); 
   try { 
       /* 
          the result of the next line is the web element; 
          the web element should be saved in a variable and 
          used in the remaining of the method:

           WebElement element = wait.until(.....); 

          also

          is visibilityOfElementLocated the best expected condition 
          if the intent is to type in the element? 
          how about using elementToBeClickable instead? 
       */ 
       wait.until(
           ExpectedConditions.visibilityOfElementLocated
                              (elementLocator)); 

       /*  
          finding the element again is useless; 
          there is no need to find the element again;  
          it was already found by the previous line; 
          just type into it 
       */ 
       driver.findElement(elementLocator).sendKeys(keyword); 

       /* 
           why display info in the console?  
           first, the console can display limited amount of text; 
           second, how is this info from the console useful 
           if you have 250 test methods? 
       */ 
       System.out.println("Entered keyword: " + keyword); 

       /*  
            why wait for 1 second?  
            why wait at all? 
            why not wait for 2, 3, 4 seconds? 
       */ 
       waitFor(1000); 
   } 
   //if there is an exception 
   catch(RuntimeException e) { 
        //same as above about logging info to console 
        System.out.println("\ntry again to type keyword:" + keyword); 

        /* 
           why wait only for 2 seconds?  
           why not 5?  or 10? 
        */ 
        wait(); 

        /*  find the element again and type the keyword 
        */ 
        driver.findElement(elementLocator).sendKeys(keyword); 

        /* 
            no idea why enter has to be pressed; 
            is the web element part of a form so we have to submit it? 
        */ 
        sendKeyEnter(); 
   } 
}

//on what element is this method typing enter?
public void sendKeyEnter(){ 
   wait.until(
       ExpectedConditions.visibilityOfElementLocated
            (elementLocator)); 
   driver.findElement(elementLocator).sendKeys(Keys.RETURN);
} 

/*  for how long does this method wait? 
    this method is duplicated with the next one; 
*/
public void wait(){ 
    try { 
       Thread.sleep(2000); 
    } 
    catch (InterruptedException e) { 
       // the autogenerated code is not removed
       // TODO Auto-generated catch block 
       e.printStackTrace(); 
    }
} 

public void waitFor(int time){ 
    try { 
       Thread.sleep(time); 
    } 
    catch (InterruptedException e) { 
       // TODO Auto-generated catch block 
       e.printStackTrace(); 
    }
} 

 

What you just saw is a case of “re-inventing the wheel”.

The code tries to find an web element that is loaded slowly in the page.

 

Selenium WebDriver has already classes that do this such as WebDriverWait and ExpectedConditions.

The same thing can be done with just 3 lines of code:

 

WebElement element = new WebDriverWait(driver, 30).until (
                           elementToBeClickable(elementLocator)); 
element.sendKeys(keyword);

 

How does this work?

The wait object will retry every 500 ms finding the element.
If the element is found and clickable, it is returned so it can be saved in a variable.
Otherwise, the wait object with try again.

Finding the element will be retried until

  • the element is found and is clickabled  or
  • the 30 seconds timeout is up

 

But lets say that we are concerned about stale exceptions or we want more control over the wait times.

Then, we can use FluentWaits:

FluentWait<WebDriver> wait = new FluentWait<WebDriver>(driver)
                        .withTimeout(30, SECONDS)
                        .pollingEvery(2, SECONDS)
                        .ignoring(NoSuchElementException.class)
                        .ignoring(StaleElementReferenceException.class) ;

WebElement element = wait.until (elementToBeClickable(elementLocator)); 

element.sendKeys(keyword);

 

So, do you want to be sure that a Selenium tester is real or fake?

Ask him to show you some of his code.

 

Use a custom driver class instead of utility classes

How can I create my own driver class in Selenium?
When learning test automation, you will want your test methods to be executed in multiple browsers such as Firefox and Chrome.

A Firefox driver is created with the FirefoxDriver class:

WebDriver driver = new FirefoxDriver();

A Chrome driver is created with the ChromeDriver class:

WebDriver driver = new ChromeDriver();

Both driver objects use the WebDriver type but are instantiated by different classes.

Now, you dont want to change the class from FirefoxDriver to ChromeDriver and back every time you want to run the tests in another browser.

Instead, you would like to create the driver based on the browser name:

WebDriver driver = new BrowserDriver(browserName);

How do we do this?

We create a custom driver class that implements the WebDriver interface and overrides its methods.

public final class BrowserDriver implements WebDriver {  

   private WebDriver driver;
   private final String browserName;
   private final int timeout = 30; 
   private final String chromeDriverPath = "./src/chromedriver.exe";  

   public BrowserDriver(String browserName) { 
      this.browserName = browserName; 
      this.driver = createDriver(browserName); 
   }  

   private WebDriver createDriver(String browserName) { 
      if (browserName.toUpperCase().equals("FIREFOX") 
         return firefoxDriver(); 

      if (browserName.toUpperCase().equals("CHROME") 
         return chromeDriver();  

      throw new RuntimeException ("invalid browser name"); 
   } 

   private WebDriver chromeDriver() { 
      if (!new File(chromeDriverPath).exists()) 
        throw new RuntimeException
                    ("chromedriver.exe does not exist!"); 

      try { 
        System.setProperty("webdriver.chrome.driver", 
                           chromeDriverPath); 
        return new ChromeDriver(); 
      } 

      catch (Exception ex) { 
        throw new RuntimeException
              ("couldnt create chrome driver"); 
      } 
   } 

   private WebDriver firefoxDriver() { 
      try { 
         return new FirefoxDriver(); 
      } 
      catch (Exception ex) {         
         throw new RuntimeException
              ("could not create the firefox driver"); 
     } 
   } 

   @Override 
   public String toString() { 
      return this.browserName; 
   } 

   public WebDriver driver() { 
      return this.driver; 
   }  

   @Override 
   public void close() { 
      driver().close();   
   }  

   @Override 
   public WebElement findElement(By locator) { 
      return driver().findElement(locator); 
   }  

   @Override 
   public List findElements(By arg0) { 
      return driver().findElements(arg0); 
   }  

   @Override 
   public void get(String arg0) { 
      driver().get(arg0); 
   }  

   @Override 
   public String getCurrentUrl() { 
      return driver().getCurrentUrl(); 
   }  

   @Override 

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

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

   @Override 
   public String getWindowHandle() { 
      return driver().getWindowHandle(); 
   }  

   @Override 
   public Set getWindowHandles() { 
     return driver().getWindowHandles(); 
   }  

   @Override 
   public Options manage() { 
      return driver().manage(); 
   }  

   @Override 
   public Navigation navigate() { 
      return driver().navigate(); 
   }  

   @Override 
   public void quit() { 
     driver().quit(); 
   }  

   @Override 
   public TargetLocator switchTo() { 
      return driver().switchTo(); 
   }  

   @Override 
   public  X getScreenshotAs(OutputType target
   throws WebDriverException { 
      return ((TakesScreenshot) driver())
                    .getScreenshotAs(target);
   } 

}

 

The only method that is not defined in the WebDriver interface is createDriver().
createDriver() creates a driver object based on the browser name and stores it in the driver variable.

All other methods just override the WebDriver interface methods.

In the test method, you can now use the new BrowserDriver class instead of FirefoxDriver and ChromeDriver:

WebDriver driver = new BrowserDriver(“CHROME”);

driver.get(“http://www.bestbuy.com”);

WebElement searchBox = driver.findElement(searchBoxLocator);

 

The custom driver class can add as well new methods.

 

Lets say that you want to have find element methods that use the WebDriverWait and ExpectedConditions classes:

public WebElement findVisibleElement(By locator) {
   WebElement element = new WebDriverWait(driver(), timeout)
                .until( visibilityOfElementLocated (locator));
   return element;
}

public WebElement findClickableElement(By locator) {
   WebElement element = new WebDriverWait(driver(), timeout)
               .until( elementToBeClickable (locator));
   return element;
}

public WebElement findHiddenElement(By locator) {
   WebElement element = new WebDriverWait(driver(), timeout)
              .until( presenceOfElementLocated (locator));
   return element;
}

You may be tempted to create a utility class, move these methods there and then use the utility class as a parent for the page classes.

Resist this temptation as it leads to bad things 🙂

Avoid utility classes in your automation projects.

More about utility classes very soon on this blog.

Instead of the utility class, you can move these methods to your custom driver class.

public final class BrowserDriver implements WebDriver {  

   private WebDriver driver; 
   private final String browserName; 
   private final int timeout = 30; 
   private final String chromeDriverPath = "./src/chromedriver.exe"; 

   public BrowserDriver(String browserName) { 
      this.browserName = browserName; 
      this.driver = createDriver(browserName); 
   }  

......................................................................

   @Override 
   public WebElement findElement(By locator) { 
     WebElement element = new WebDriverWait(driver(), timeout) 
                        .until( visibilityOfElementLocated(locator)); 
     return element; 
   }  

   @Override 
   public List findElements(By arg0) { 
     return driver().findElements(arg0); 
   }

   public WebElement findClickableElement(By locator) { 
      WebElement element = new WebDriverWait(driver(), timeout) 
                       .until( elementToBeClickable(locator)); 
      return element; 
   } 

   public WebElement findHiddenElement(By locator) { 
      WebElement element = new WebDriverWait(driver(), timeout) 
                      .until( presenceOfElementLocated(locator)); 
      return element; 
 }

}

How to retry automatically Selenium tests

Test automation methods fail often.

Sometimes because the test automation code has bugs so you need to fix them.

Other times because the tested site does not load correctly or loads very slowly.

 

Lets look in more detail at this second case.

 

The test automation code is correct.

You prove it correct by running the test multiple times.
If the test method fails a lot (4-5 times out of 10), the problem is in the automation code.
If the test method fails once every 15 or 20 runs, the site is at fault.

 

These rare and random failures are not real failures.

It would be good if we could re-run failed test methods automatically a few times before declaring them as failed.

How can we do this?

Lets start with a simple test class with 3 test methods:

public class TestRetry {

  @Test
  public void testPassed() {
    System.out.println("test passed!");
    assertTrue(true);
  }

  @Test
  public void testFailed() {
    System.out.println("test failed!");
    assertTrue(false);
  }

  @Test
  public void testMayFail() {
    int i = new Random().nextInt(10);
    System.out.println("test may fail! - " + i);
    if (i > 5)
       assertTrue(false);
    else
       assertTrue(true);
  }
}

 

The first method passes all the time.

The second method fails all the time.

The third method fails sometimes.

 

We want to be able to re-run the third test method automatically every time it fails.

 

For this purpose, 2 new classes are created:

  1. RetryAnalyzer (which implements the IRetryAnalyzer TestNG interface)
  2. RetryTestListener (which extends the TestListenerAdapter TestNG class)

 

RetryAnalyzer uses a variable for the current retry count.

It has also a variable for the maximum retry count.

Its retry() method checks if the current retry count is smaller than the max count.

If it is, the current count is increased by 1 and the method returns true.

Otherwise, the method returns false.

 

import org.testng.IRetryAnalyzer;

import org.testng.ITestResult;


public class RetryAnalyzer implements IRetryAnalyzer  { 

  private int count = 0; 

  private int maxCount = 2;

  @Override

  public boolean retry(ITestResult result) { 

    if(count < maxCount) {  

       count++;

       return true;        

    }        

    return false

 }

}

 

The RetryAnalyzer class does not do anything by itself.

It needs to be used with the RetryTestListener class.

 

RetryTestListener overrides the onTestFailure() method so that

  1. when the script fails, the script is retried
  2. if the retry count is less than the max count, the script’s status is changed to skipped
  3. if the retry count is equal to max count, the script fails and the script’s status is failed

 

import org.testng.ITestResult;

import org.testng.Reporter;

import org.testng.TestListenerAdapter;

public class RetryTestListener extends TestListenerAdapter  {

@Override
public void onTestFailure(ITestResult result {

     Reporter.setCurrentTestResult(result);

     if(result.getMethod().getRetryAnalyzer().retry(result))                         result.setStatus(ITestResult.SKIP);

     Reporter.setCurrentTestResult(null);

  }

}

 

The test class can be updated now to use the new classes.

The listener is attached to the test class using the @Listeners annotation.

The third test method is updated by adding the retryAnalyzer attribute to the @Test annotation.

 

import static org.testng.Assert.assertTrue;

import java.util.Random;

import org.testng.annotations.Listeners;

import org.testng.annotations.Test;

@Listeners(RetryTestListener.class)

public class TestRetry {

  @Test

  public void testPassed() {

      System.out.println("test passed!");

      assertTrue(true);

  }

  @Test

  public void testFailed() {

      System.out.println("test failed!");

      assertTrue(false);

  }

  @Test(retryAnalyzer=RetryAnalyzer.class)

  public void testMayFail() {

       int i = new Random().nextInt(10);

       System.out.println("test may fail! - " + i);

       if (i > 5)

             assertTrue(false);

      else

             assertTrue(true);

   }

}

 

If the third method fails, its status is set to SKIPPED and the method is retried.

 

For the first retry, if the method passes, the method’s execution ends with the status PASSED.

If the method fails again, the method’s status is set to SKIPPED and the method is retried (retry count = 1).

 

For the second retry, if the method passes, the method’s ends with the status PASSED.

If the method fails, the method’s status is changed to SKIPPED and the method is retried (retry count = 2).

 

Finally, If the method passes, the method’s ends with the status PASSED.

If the method fails, the method’s status is changed to FAILED.

How to create page objects with Selenium WebDriver

Everything starts with a manual test case. 

Our test case is for the Vancouver Public Library site

It tests that a search done on the site’s home page returns results. 

 

 

 

The test case tell us what actions the user takes 

1. user opens home page 
2. user searches for keyword 

to verify if the result is correct

3. user verifies that results page is opened 
4. user verifies that there is at least 1 result for the search 

Our goal is to automate this test case using Selenium WebDriver, Java and the page object model.

Before writing any code, we will convert the test case to a format suitable for test automation. 

This will happen in a few iterations. 


1. PREPARE THE TEST CASE FOR AUTOMATION

1.1 GROUP TEST CASE ACTIONS BY PAGE

First, we group the test case actions and verifications by the page where they happen:

HOME PAGE 

1. open home page 
2. search for keyword 

RESULTS PAGE 

3. verify that results page is opened 
4. verify that there are results for the search 

1.2 BREAK DOWN TEST CASE ACTIONS IN SUB-ACTIONS

The test case actions tell us WHAT the user does to be able to make verifications.

Both the user actions and the verifications are defined at a high level so far. 

We will decompose each test case action in sub-actions that describe HOW the action is executed by the user. 

When decomposing actions in sub-actions, we will determine as well what is required by each sub-action. 


HOME PAGE 

1.  OPEN HOME PAGE

    Required: home page url 

2. SEARCH FOR KEYWORD 

   2.1 click search text box

         Required: search text box locator

   2.2 type keyword in search text box

         Required: search text box locator, search keyword

   2.3 click search button 

         Required: search button locator




RESULTS PAGE

3. CHECK THAT RESULTS PAGE IS DISPLAYED 

   3.1 get page title 

         Required: page title 

   3.2 verify that page title is correct 

   3.3 get page url

         Required: page url 

   3.4 verify that page url is correct 


4. CHECK THAT THE SEARCH RETURNS RESULTS

   4.1 get results count value 

         Required: result count label locator

4.2 verify that results count value is > 0

 




We have so far 3 types of sub-actions:

  • sub-actions that do something 

1.1 open page using home page url

2.1 click search text box

2.2 type keyword in search text box

2.3 click search button

  • sub-actions that provide information about page elements 

3.1 get page title

3.3 get page url

4.1 get results count value

  • actions that assert (verify) that information is correct 

3.2 verify that page title is correct

3.4 verify that page url is correct

4.2 verify that results count value is > 0




1.3 CONVERT ACTIONS AND SUB-ACTIONS INTO METHODS

Each action and sub-action will be implemented by a method as follows:

  1. If the action does something. the corresponding method does not return a value.
  2. If the action provides information, the corresponding method returns a value.
  3. If the action verifies that information is correct, an assertion is used.

Methods may have parameters (example: search() method uses a parameter for the keyword).

If a method interacts with a page element, it will use a locator variable for the element.

HOME PAGE 

1. public void openPage(String homePageUrl) 

2. public void searchForKeyword(String keyword) 

This method executes the search with the help of 2 other methods:

typeSearchKeyword(keyword);
executeSearch();

These methods use 2 locator variables:

String searchTextBoxLocator;
String searchButtonLocator;



RESULTS PAGE

3. verifyThat(getPageTitle() == expectedResultsPageTitle);

     verifyThat(resultsPageUrl == expectedResultsPageUrl); 

getPageTitle() provides current page title.

             getPageUrl() provides the current page url.

      
 4. verifyThat(resultCount() > 0); 

     resultCount() provides the count of results.

     It uses a locator variable for the results count label (resultCountLabelLocator).



We have what we need for the test automation script:

  • methods to be implemented
  • variables that the methods need
  • methods and variables are grouped by page

3 components are required for the test automation script to work: 

  • Test Class
  • Home Page Class
  • Results Page Class



2. CREATE THE TEST CLASS

Each test automation script implements one test case. 

A test script is created in a test class which may include multiple test scripts. 

If a test script is equivalent of a test case, a test class is equivalent of a suite of test cases. 




2.1 Component of a test class 

The components of the test class are

 

a) setUpEnvironment() method 

A test script needs a test environment to be executed in.

The test environment consists in

1. browser where the site is loaded

2. browser driver for driving the site in the browser

setUpEnvironment() method creates the test environment for the test script.

It executes before each test script because of the @Before JUNIT annotation.

It creates the browser driver object which has as effect opening the browser.

 

b) testScript() 

The test script implements a test case.

It uses @Test JUNIT annotation which specifies that the method is a test script.

You can have a test script for each manual test case to be automated.


c) cleanUpEnvironment() 

After the test script finished executing, the test environment is cleaned up.

The cleanUpEnvironment() method is responsible for this.

It executes after each test script because of the @After JUNIT annotation.

It destroys the browser driver object and closes the browser.



2.2 How a test script is executed 


When executed, a test script goes through a few phases:



test environment is built by setUpEnvironment() method

the site is brought to the point where verifications can be done.

 

verifications are being done.

 

test environment is cleaned up by cleanUpEnvironment() method.


 

2.3 Create the template of the test class 

What follows is the template of the test class.

The setUpEnvironment(), cleanUpEnvironment() and searchReturnsResults() method are empty at this point.

We will add code to them very soon.

public class Tests {

@Before
public void setUpEnvironment() {
}

@Test
public void searchReturnsResults() {
}

//other test scripts

@After
public void cleanUpEnvironment() {
}
}




2.4 Add code for setUpEnvironment() and cleanUpEnvironment() methods 

3 changes are done in this phase: 

  • WebDriver variable is added to test class
  • the driver object is instantiated as FirefoxDriver() in the setUpEnvironment() method
  • the driver object is destroyed using driver.quit() in the cleanUpEnvironment() method 
public class Tests {

WebDriver driver;

@Before
public void setUpEnvironment() {
  driver = new FirefoxDriver();
}

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

@Test
public void searchReturnsResults() {
}

}



2.5 Add the code for the test script 

Next, we add code to the test script. 

The code should look as close as possible to the test case. 

We will use in the test script the methods for the test case actions (openPage(), searchForKeyword()). 

public class Tests {

WebDriver driver;

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

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

@Test
public void searchReturnsResults() {
  homePage.open("http://www.vpl.ca");
  homePage.searchForKeyword("java");
  assertTrue(resultsPage.isOpen() == true);
  assertTrue(resultsPage.resultCount() > 0);
}

}




2.6 Create objects for Home Page and Results Page 

For the test script to work, the homePage and resultsPage objects are needed. 

They are objects of HomePage and ResultsPage classes. 

public class Tests {

WebDriver driver;

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

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

@Test
public void searchReturnsResults() {

  HomePage homePage = new HomePage(driver);
  homePage.openPage("http://www.vpl.ca");
  homePage.searchForKeyword("java");

  ResultsPage resultsPage = new ResultsPage(driver);
  assertTrue(resultsPage.isOpen() == true);
  assertTrue(resultsPage.resultCount() > 0);

}

}



If you execute the code at this point, it will not work as the HomePage and ResultsPage classes do not exist yet.


What is important to note for the test script is that

  • it does not include any Selenium WebDriver method or objects; the only exception to this rule is the driver variable 
  • it uses objects of the HomePage and ResultsPage classes to interact with these pages of the site
  • it uses JUNIT assertions for verifications 




3. CREATE THE HOME PAGE CLASS 

HomePage class is the container where user actions for Home Page are implemented. 

It is a page object class. 

It includes fields and methods. 


3.1 Create the template of the home page class

public class HomePage {

/* VARIABLES

  element locators
  page title
  page url

*/

/* METHODS

   openPage(url);
   searchForKeyword(keyword);

*/

}



3.2 Add the fields and methods to the class 

public class HomePage {

//fields
String homePageUrl;
String searchTextBoxLocator;
String searchButtonLocator;

//methods
public void openPage(String url) {
}

public void searchForKeyword(String keyword) {
}

}



3.3 Add values to the class’s fields

public class HomePage {

String homePageUrl = "http://www.vpl.ca";
String searchTextBoxId = "globalQuery";
String searchButtonLocator = "//input[@class='search_button']";

public void openPage(String url) {
}

public void searchForKeyword(String keyword) {
}

}



3.4. Add the WebDriver field and constructor to the class 

The HomePage constructor executes automatically when a class object is created. 

It takes a WebDriver parameter (driver) that comes from the test script. 

The driver parameter is saved in the browserDriver class field. 
The HomePage methods use the browserDriver to interact with page elements through Selenium WebDriver methods.

public class HomePage {

WebDriver browserDriver;

String homePageUrl = "http://www.vpl.ca"; 
String searchTextBoxId = "globalQuery";
String searchButtonLocator = "//input[@class='search_button']";

public HomePage (WebDriver driver) {
  browserDriver = driver;
}

public void openPage(String url) {
}

public void searchForKeyword(String keyword) {
}

}



3.5. Implement the methods 

HomePage class uses 2 types of methods: 

PUBLIC

        These methods correspond to test case actions and are used in the test script:

            openPage(String url) 

            searchForKeyword(String keyword) 


PRIVATE

       These methods are helper methods for the public methods.

       They correspond to sub-actions and are used in the HomePage class only:

           typeSearchKeyword(String keyword) 

           executeSearch() 

public class HomePage {

WebDriver browserDriver;

String homePageUrl = "http://www.vpl.ca";
String searchTextBoxId = "globalQuery";
String searchButtonLocator = "//input[@class='search_button']";

public HomePage (WebDriver driver) {
  browserDriver = driver;
}

public void openPage(String url) {
  browserDriver.get(homePageUrl);
}

public void searchForKeyword(String keyword) {
 typeSearchKeyword(keyword);
 executeSearch();
}

private void typeSearchKeyword(String keyword) {
 WebElement searchTextBox = browserDriver.findElement(
                              By.id(searchTextBoxId));
 searchTextBox.click();
 searchTextBox.clear();
 searchTextBox.sendKeys(keyword);
}

private void executeSearch() {
 WebElement searchTextButton = browserDriver.findElement(
                                 By.xpath(searchButtonLocator));
 searchTextButton.click();
}

}


Quick explanation of how the methods work:

openPage(url)

  1. opens the url in the browser

typeSearchKeyword(String keyword) 

  1. finds search textbox element using its locator
  2. clicks search textbox element
  3. clears existing value from the textbox (if any)
  4. types keyword in the textbox

executeSearch()

  1. finds search button element using its locator
  2. clicks search button element

searchForKeyword(keyword)

  1. types keyword in search text box using the typeSearchKeyword() method
  2. executes the search using the executeSearch() method





4. CREATE THE RESULTS PAGE CLASS 

ResultsPage class is the container where all user actions for Results Page are implemented. 

It is a page object class. 

It includes fields and methods. 



4.1. Create the template of the results page

public class ResultsPage {

/* FIELDS

   element locators
   page title
   page url

*/

/* METHODS

   isOpen()
   resultCount()

*/

}



4.2. Add the fields and methods to the class 

public class ResultsPage {

//fields
String resultCountLocator;
String resultsPageTitle;
String resultsPageUrl;

//methods
public boolean isOpen() {
}

public int resultCount() {
}

}



4.3. Add values to the fields of the class 

public class ResultsPage {

String resultCountLocator = "//span[@class='items_showing_count']";
String resultsPageTitle = "Search | Vancouver Public Library | BiblioCommons";
String resultsPageUrl = "https://vpl.bibliocommons.com/search?q=java&t=keyword";

public boolean isOpen() {
}

public int resultCount() {
}

}



4.4. Add the WebDriver field and constructor to the class 

ResultsPage constructor executes automatically when a class object is created. 

It takes a WebDriver parameter (driver) that comes from the test script. 

The driver parameter is saved in the browserDriver class field. 

ResultsPage methods use browserDriver to interact with page elements through Selenium WebDriver methods. 

public class ResultsPage {

WebDriver browserDriver;

String resultCountLocator = "//span[@class='items_showing_count']";
String resultsPageTitle = "Search | Vancouver Public Library | BiblioCommons";
String resultsPageUrl = "https://vpl.bibliocommons.com/search?q=java&t=keyword";

public ResultsPage(WebDriver driver) {
  browserDriver = driver;
}

public boolean isOpen() {
}

public int resultCount() {
}

}



4.5. Implement the methods 

public class ResultsPage {

WebDriver browserDriver;

String resultCountLocator = "//span[@class='items_showing_count']";
String expectedResultsPageTitle = "Search | Vancouver Public Library | BiblioCommons";
String expectedResultsPageUrl = "https://vpl.bibliocommons.com/search?q=java&t=keyword";

public ResultsPage(WebDriver driver) {
  browserDriver = driver;
}

public boolean isOpen() {
  boolean isTitleCorrect = browserDriver.getTitle()
                                        .equalsIgnoreCase(
                                            expectedResultsPageTitle);
  boolean isUrlCorrect = browserDriver.getCurrentUrl()
                                      .equalsIgnoreCase(
                                            expectedResultsPageUrl);

  return isTitleCorrect && isUrlCorrect;
}

public int resultCount() {
  WebElement resultCountLabel = browserDriver.findElement(
                                   By.xpath(resultCountLocator));    

  String resultCountText = resultCountLabel.getText();
  return extractNumberFromResultCountText(resultCountText);
}

private int extractNumberFromResultCountText(String resultCountText) {
  int startIndex = resultCountText.indexOf("of") + 3;
  int endIndex = resultCountText.indexOf(" items");
  return Integer.parseInt(resultCountText
                            .substring(startIndex, endIndex));
}

}



Quick explanation of how the methods work:

isOpen()

  1. gets the page title and compares it with the expected value
  2. gets the page url and compares it with the expected value
extractNumberFromResultCountText(resultCountText)
  1. extracts the number from the result count label text
resultCount()
  1. finds the result count label element
  2. gets the value of the result count label element
  3. extracts the number from the result count label
  4. returns the number
 
 This is it!
 You should have a good idea now about how to use the page object model to create page object classes.

Questions or feedback?
Please leave them in the Comments section!

What is next?

Page object model is much more than creating classes for all pages of the site.

For good test automation code, you should also learn about

  1. returning page objects from page methods
  2. creating page element classes
  3. creating a base class for a generic page
  4. using Page Factory
  5. using the LoadableComponent class
  6. using the SlowLoadableComponent class

 

I am using all these in my daily automation work and I think that you should use them too.

Read more here.

How to reduce code duplication using predicates and lambda expressions

Many sites allow their clients to search for information and then browse through the results.

Each result has multiple attributes such as

  • title
  • price
  • status

 

results attributes

 

In test automation projects, it is important to have the ability of filtering the results by different attributes.

For example, filter the results with price greater than 0.

Or filter the results that are available only online.

Or filter the results that have a keyword included in the title.

 

How can this filtering be implemented?

public class Result {

  private String title;
  private int price;
  private String status;

  public Result(String title, int price, String status) {
    this.title = title;
    this.price = price;
    this.status = status;
  }

  public int price() {
    return this.price;
  }

  public String title() {
    return this.title;
  }

  public String status() {
    return this.status;
  }

}

Result class is used for creating an object for each result returned for the user search.

The results filtering happens in the test class:

 

public class TestClass {

List<Result> results;

List<Result> withPriceGreaterThan0(List<Result> list) {
  List<Result> newResults = new ArrayList<>();
  for (Result r : list)
    if (r.price() > 0)
     newResults.add(r);
  return newResults;
}

List<Result> withPriceEqualTo0(List<Result> list) {
  List<Result> newResults = new ArrayList<>();
  for (Result r : list)
    if (r.price() == 0)
     newResults.add(r);
  return newResults;
}

List<Result> byStatus(List<Result> list, String status) {
  List<Result> newResults = new ArrayList<>();
  for (Result r : list)
    if (r.status().toLowerCase().contains(status.toLowerCase()))
     newResults.add(r);
  return newResults;
}

List<Result> byKeyword(List<Result> list, String keyword) {
  List<Result> newResults = new ArrayList<>();
  for (Result r : list)
    if (r.title().toLowerCase().contains(keyword.toLowerCase()))
     newResults.add(r);
  return newResults;
}

@Before
public void setUp() {
  Result r1 = new Result("Otter Box", 54, "Not sold online");
  Result r5 = new Result("Virgin iPhone", 0, "Not sold online");
  Result r7 = new Result("Apple iPhone", 450, "Not sold online");
  Result r2 = new Result("Rogers iPhone", 130, "Sold out online");
  Result r6 = new Result("SanDisk iXpand", 125, "Sold out online");
  Result r8 = new Result("Koodo iPhone", 280, "Sold out online");
  Result r3 = new Result("Fido iPhone", 400, "Available online only");
  Result r4 = new Result("TELUS iPhone", 0, "Available online");

  results = Arrays.asList(r1, r2, r3, r4, r5, r6, r7, r8);
}

@Test
public void test1() {

  for(Result r: withPriceGreaterThan0(results))
    assertTrue(r.price() > 0);

  for(Result r: withPriceEqualTo0(results))
    assertTrue(r.price() == 0);

  for(Result r: byStatus(results, "AVAILABLE ONLINE"))
    assertTrue(r.status().toLowerCase().contains("available online"));

  for(Result r: byKeyword(results, "IPHONE"))
    assertTrue(r.title().toLowerCase().contains("iphone"));

}

}

The filtering is being done by the following methods:

List<Result> withPriceGreaterThan0(List<Result> list
List<Result> withPriceEqualTo0(List<Result> list)
List<Result> byStatus(List<Result> list, String status
List<Result> byKeyword(List<Result> list, String keyword)

All get a List<Result> parameter, browse the list with a for statement and select the results that match a specific condition.

The code of the 4 filtering methods is pretty much identical with the only difference being the condition that the results are compared against:

r.price() > 0

r.price() == 0

r.status().toLowerCase().contains(status.toLowerCase())

r.title().toLowerCase().contains(keyword.toLowerCase())

 

How can we have 1 filtering method instead of 4?

One of the new features of Java 8 is lambda expressions.

lambda expressions

Lambda expressions and predicates will help us replace the 4 methods with 1.

Lambda expressions allow functionality (code) to be used as a method parameter in addition to objects.

A lambda expression can be assigned to a Predicate object which can be used as value for a method parameter.

If you are not familiar with lambda expressions yet, this is a good introduction.

Our code can be rewritten with predicates and lambda expressions as follows:

import static org.junit.Assert.assertTrue;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;

import org.junit.Before;
import org.junit.Test;

public class TestClass {

List<Result> results;

List<Result> filter(List<Result> list, Predicate<Result> p) {
  List<Result> newResults = new ArrayList<>();
  for (Result r : list)
    if (p.test(r))
      newResults.add(r);
  return newResults;
}

@Before
public void setUp() {
 ..............................
}

@Test
public void test1() {

  Predicate<Result> priceGreaterThan0 = (r) -> r.price() > 0;
  for(Result r: filter(results, priceGreaterThan0))
    assertTrue(r.price() > 0);

  for(Result r: filter(results, (r) -> r.price() == 0))
    assertTrue(r.price() == 0);

  for(Result r: filter(results, (r) -> r.status().equalsIgnoreCase("AVAILABLE ONLINE") == true))
    assertTrue(r.status().toLowerCase().contains("available online"));

  Predicate<Result> keywordInTitle = (r) ->   r.title().toUpperCase().contains("IPHONE") == true;
  for(Result r: filter(results, keywordInTitle))
    assertTrue(r.title().toLowerCase().contains("iphone"));

  for(Result r: filter(results, priceGreaterThan0.and(keywordInTitle))) {
    assertTrue(r.price() > 0);
    assertTrue(r.title().toLowerCase().contains("iphone"));
  }

}

}

In this version of the code, all conditions are expressed as lambda expressions and can be assigned or not to Predicate objects:

Predicate<Result> priceGreaterThan0 = (r) -> r.price() > 0;

Predicate<Result> keywordInTitle = (r) -> r.title().toUpperCase().contains("IPHONE") == true;

(r) -> r.price() == 0))

(r) -> r.status().equalsIgnoreCase("AVAILABLE ONLINE") == true))

Since the conditions are expressed as lambda expressions that can be assigned to a Predicate<Result> object, we can have 1 filtering method only that gets 2 parameters:

  • list of results
  • predicate

 

Can we make the code simpler?

The predicates can be moved to a separate class as follows:

import java.util.function.Predicate;

import Result;

public class Predicates {

  public static Predicate<Result> priceGreaterThan0() {
    return (r) -> r.price() > 0;
  }

  public static Predicate<Result> priceEqualTo0() {
    return (r) -> r.price() == 0;
  }

  public static Predicate<Result> availableOnline() {
    return (r) -> r.status().equalsIgnoreCase("AVAILABLE ONLINE") == true;
  }

  public static Predicate<Result> soldOutOnline() {
    return (r) -> r.status().equalsIgnoreCase("SOLD OUT ONLINE") == true;
  }

  public static Predicate<Result> titleIncludes(String keyword) {
    return (r) -> r.title().toUpperCase().contains(keyword) == true;
  }

}

The test class becomes straightforward when the predicates are in their own class:

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;

import static org.junit.Assert.assertTrue;

import org.junit.Before;
import org.junit.Test;

import Result;

public class TestClass {

List<Result> results;

List<Result> filter(List<Result> list, Predicate<Result> p) {
   List<Result> newResults = new ArrayList<>();
   for (Result r : list)
     if (p.test(r)) 
       newResults.add(r);
   return newResults;
 }

@Before
public void setUp() {

...........................................

}

@Test
public void test1() {

for(Result r: filter(results, Predicates.priceGreaterThan0()))
  assertTrue(r.price() > 0);

for(Result r: filter(results, Predicates.priceEqualTo0()))
  assertTrue(r.price() == 0);

for(Result r: filter(results, Predicates.availableOnline()))
  assertTrue(r.status().toLowerCase().contains("available online"));

for(Result r: filter(results, Predicates.titleIncludes("iphone")))
  assertTrue(r.title().toLowerCase().contains("iphone"));

for(Result r: filter(results, Predicates.priceGreaterThan0()
.and(Predicates.titleIncludes("iphone")))) {
  assertTrue(r.price() > 0);
  assertTrue(r.title().toLowerCase().contains("iphone"));
}

}

}

 

How to run scripts in a specific browser with Maven

mvn test is the Maven command for running test scripts in command prompt (or Jenkins).

It runs all test scripts from the test class in the browser defined in the project.

But what if you would like to specify the browser name as a parameter of the command?

Such as

mvn test -Dbrowser=chrome

or

mvn test -Dbrowser=firefox

How can this be done?

There are 3 parts to it.

 

Create a custom driver class

The custom driver class should implement the WebDriver interface and all its methods.

Its constructor has browser name as parameter and it creates the appropriate web driver.

 

import java.util.List;
import java.util.Set;

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.firefox.FirefoxDriver;
import org.openqa.selenium.ie.InternetExplorerDriver;

public class Driver implements WebDriver {

WebDriver driver;
String browserName;

public Driver(String browserName) {
  this.browserName = browserName;

  if (browserName.equalsIgnoreCase("chrome"))
   this.driver = new ChromeDriver();

  if (browserName.equalsIgnoreCase("firefox"))
   this.driver = new FirefoxDriver();

  if (browserName.equalsIgnoreCase("ie"))
   this.driver = new InternetExplorerDriver();

}

public void close() {
  this.driver.close();

}

public WebElement findElement(By arg0) {
  return this.driver.findElement(arg0);
}

public List<WebElement> findElements(By arg0) {
  return this.driver.findElements(arg0);
}

public void get(String arg0) {
  this.driver.get(arg0);

}

public String getCurrentUrl() {
  return this.driver.getCurrentUrl();
}

public String getPageSource() {
  return this.driver.getPageSource();
}

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

public String getWindowHandle() {
  return this.driver.getWindowHandle();
}

public Set<String> getWindowHandles() {
  return this.driver.getWindowHandles();
}

public Options manage() {
  return this.driver.manage();
}

public Navigation navigate() {
  return this.driver.navigate();
}

public void quit() {
  this.driver.quit();
}

public TargetLocator switchTo() {
  return this.driver.switchTo();
}

 

Read the browser property from Maven command

The maven command looks like

mvn test -Dbrowser=chrome

Browser is a system property that can be read in the setUp() method of the test class:

String browserName = getParameter("browser");

 

Create the driver based on the browser name

This is done as well in the setUp() method of the test class:

 

import static org.junit.Assert.*;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;

public class TestsMaven {

 Driver driver;

 @Before
 public void setUp() {
  String browserName = getParameter("browser");
  driver = new Driver(browserName);
 }

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

 private String getParameter(String name) {
  String value = System.getProperty(name);
  if (value == null)
     throw new RuntimeException(name + " is not a parameter!");

  if (value.isEmpty())
    throw new RuntimeException(name + " is empty!");

  return value;
 }

 @Test
 public void test1() {
  driver.get("http://www.vpl.ca");
 }

}

}

 

You can now execute the maven command in command prompt so that the tests run in a specific browser.

Correct and incorrect ways of creating page object classes

Let’s say that you want to automate test scenarios for the login page of a web site.

The login page has 3 elements:

– username textbox

– password textbox

– sign in button

 

login

 

Some of the test cases to be automated for this page are

  1. user can sign in with correct username and password
  2. user cannot sign in if username or password are incorrect
  3. password is masked
  4. user can create a new account

 

In this article, we will focus on the first test case only.

Since the test cases uses 2 pages (login page and the page the user sees after a successful sign in), we will need 2 page object classes:

  1. LoginPage class
  2. MainPage class

One way of implementing the LoginPage.java class is

public class LoginPage {

private WebDriver driver;

private WebDriverWait wait;

private By userNameLocator = By.id("userNameId");

private By passwordLocator = By.id("passwordId");

private By signInLocator   = By.id("signInId");

 

public LoginPage(WebDriver driver) {

   this.driver = driver;

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

}

public void clickUserName() {

   WebElement userName = findElement(userNameLocator);

   userName.click();

}

public void clickPassword() {

   WebElement password = findElement(passwordLocator);

   password.click();

}

public void clickSignIn() {

   WebElement signIn = findElement(signInLocator);

   signIn.click();

}

public void typeUserName(String value) {

   WebElement userName = findElement(userNameLocator);

   userName.sendKeys(value);

}

public void typePassword(String value) {

   WebElement password = findElement(passwordLocator);

   password.sendKeys(value);

}

private WebElement findElement(By locator) {

  return

  wait.until(visibilityOfElementLocated(locator));

}

 

The LoginPage class has multiple methods for clicking elements and typing text into them.

Using this LoginPage class, we can now build a test script (JUNIT):

@Test

public void testSuccessfullLogin() {

  LoginPage loginPage = new LoginPage(driver);

  loginPage.clickUserName();

  loginPage.typeUserName("admin");

  loginPage.clickPassword();

  loginPage.typePassword("abcdef");

  loginPage.clickSignIn();

  MainPage mainPage = new MainPage(driver);

  assertTrue(mainPage.isDisplayed() == true);

}

 

Building a page object class like this is not correct.

It is not correct because what we do is creating an API that interacts with HTML elements.

For a page that has only 3 elements, we have 5 methods.

Imagine how many methods you will need for a page with 100 elements.

 

The correct approach for page object classes is explained in the following link:

https://martinfowler.com/bliki/PageObject.html

We should not create page object classes and APIs that interact with HTML elements.

Instead, we should create page classes and APIs that interacts with the site similar with how a user does it.

 

 

pageobject

 

See below the LoginPage class changed to interact with the site:

public class LoginPage {

private WebDriver driver;

private WebDriverWait wait;

private By userNameLocator = By.id("userNameId");

private By passwordLocator = By.id("passwordId");

private By signInLocator = By.id("signInId");

 

public LoginPage(WebDriver driver) {

 this.driver = driver;

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

}

public void signInWith(String userName, String password) {

 WebElement userName = findElement(userNameLocator);

 userName.click();

 userName.sendKeys(value);

 WebElement password = findElement(passwordLocator);

 password.click();

 password.sendKeys(value);

 WebElement signIn = findElement(signInLocator);

 signIn.click();

}

private WebElement findElement(By locator) {

  return

  wait.until(visibilityOfElementLocated(locator));

}

}

The class has now 1 method only instead of 5.

This method is about interacting with the site like a user by signing in.

Using the LoginPage class, the test script becomes

@Test

public void testSuccessfullLogin() {

  LoginPage loginPage = new LoginPage(driver);

  loginPage.signInWith("admin", "abcdef");

  MainPage mainPage = new MainPage(driver); 

  assertTrue(mainPage.isDisplayed() == true);

}

 

The test script is significantly shorter now.

It also looks like English and is similar to a test case.

We should always aim to

  1.  making the test scripts as short as possible
  2.  moving code from the test script to the page object class
  3.  not using any webdriver object or method in the test scripts
  4.  use only page objects and page methods in the test scripts
  5.  not creating any methods that interact with an element in the page object classes
  6.  keep page object classes short (less than 250 lines of code)
  7.  do not create methods that include in their names the following words: click, type, getText, etc as these are most probably interacting with single elements

How to deal with windows authentication popups

auth-required-basic

 

If you dont like Bugs Bunny, maybe you should not continue reading.

 

What is the first thing that I do every morning at work?

Check the results of the automation scripts executed in Jenkins over night.

I want to know how many scripts passed or failed because of the work done in the previous day.

Most days, some scripts pass and some fail.

But recently, most scripts failed.

This was very unusual so I looked into it right away.

All failures followed the same pattern: the scripts failed on opening the site in the browser.

 

Bugs-Bunny-Looking-Shocked

 

 

A sample script looks like this:

@Test
public void canBrowseThroughPages() {

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

  ResultsPage resultsPage = homePage.search(keyword);

  assertTrue(resultsPage.resultCount() > 0);
 
  resultsPage = resultsPage.selectNextPage();

  assertTrue(resultsPage.isCurrentPage(2));
  assertTrue(resultsPage.resultCount() > 0);

}

 

The error happened in all scripts on the second line:
homePage.open();

The error info was

java.lang.object.RuntimeException:
home page didnt load correctly:
expected url = http://www.testsite.com
actual url =

 

I looked at the code of the open() method:

public class HomePage {

  private WebDriver driver;
  private WebDriverWait wait;

  private String url = "http://www.testsite.com";

  public HomePage(WebDriver driver) {
    this.driver = driver;
    wait = new WebDriverWait(this.driver, 30);
  }

  public void open()

    try {
      this.driver.get(url);
    }
    catch (Exception ex) {
     throw new RuntimeException("home page cannot be opened!");
    }

    try {
     wait.until(urlContains(url));
    }
    catch (Exception ex) {
     throw new RuntimeException("home page didnt load correctly - " +
             "expected url = " + url +
             " - " +
             "actual url = " + this.driver.getCurrentUrl());
    }
  }

 

I could not find anything wrong in the method.

It tries to open the url.
If it cannot, it throws an exception.

If the url is opened, it tries to verify the page url.
If it cannot, it throws an exception.

 
So, I looked again at the exception info:

java.lang.object.RuntimeException:
home page didnt load correctly;
expected url = http://www.testsite.com
actual url =

 

The actual url is empty which means that the site was not loaded.

But, why is the second exception thrown instead of the first one?

If driver.get(url) does not work, why doesn’t it generate an exception?

 

bugs bunny puzzled

Strange, isn’t it?

 

Next, I execute the script locally in Eclipse and it works.

So, the script works locally but has an error when executed in Jenkins.

 

What is the issue?

I look at the Jenkins execution details and get the name of the Jenkins slave where the script ran.

I start a remote connection to that server, open Chrome and load the site url in it.

And surprise!

A windows authentication popup is displayed!!!

That’s why the second exception was thrown instead of the first.

When executing driver.get(url), before the url is launched in the browser, the windows authentication popup is displayed.

driver.get() passes and the code continues with the url validation.

Since the authentication popup is still displayed, the page is not loaded and the url validation fails.

So the authentication popup is the culprit.

 

How can I disable the windows authentication popup?

Resetting the chrome browser settings does not help.

I can try creating the chrome driver with options and adding the site url to the chrome authentication whitelist.

This works manually.

When added to the code, the authentication popup is still there:

ChromeOptions options = new ChromeOptions();
options.addArguments("auth-server-whitelist='www.testsite.com'");
ChromeDriver driver = new ChromeDriver(options);

 
What else can I do?

Nothing goes right ……………….

So …………..

 

nothing goes right

 

Lets go left 🙂

 

I can include the account’s username and password in the url:

public class HomePage {

  private WebDriver driver;
  private WebDriverWait wait;

  private String url = "http://www.testsite.com";

  public HomePage(WebDriver driver) {
    this.driver = driver;
    wait = new WebDriverWait(this.driver, 30);
  }

  public void open() {

    try {
      this.driver.get(urlWithAccount(url));
    }
    catch (Exception ex) {
      throw new RuntimeException("home page cannot be opened");
    }

    try {
      wait.until(ExpectedConditions.urlContains(url);
    }
    catch (Exception ex) {
      throw new RuntimeException("home page didnt load correctly - " +
                                 "expected url = " + url +
                                 " - " +
                                 "actual url = " +                                                             this.driver.getCurrentUrl());
    }

  }

  private urlWithAccount(String url) {
     String username = System.getProperty("username");
     String password = System.getProperty("password");

     String newUrl = url.replace("http://",
                                 "http://" +
                                 username +
                                 ":" +
                                 password +
                                 "@");

     return newUrl;
  }

 

With the new method, the url would changes from

http://www.testsite.com

to

http://username:password@www.testsite.com

 

Rerunning the Jenkins scripts again showed that the problem is resolved.

 

thats all folks

How to use locators with parameters

Sometimes, you need to interact in automation scripts with web elements that have similar HTML code.

For example, you may want to automate the following test case:

  1. open site
  2. search for keyword
  3. check that the results page is displayed
  4. select the 2nd page of results
  5. check that the 2nd page is displayed
  6. select the 3rd page of results
  7. check that the 3rd page is displayed

 

The elements for the 2nd and 3rd pages have similar HTML code (A tag, HREF and DATE-PAGE attributes):

page 2
<a href=”/catalog/category.aspx?category=29059;page=2″ data-page=”2″>2</a>

page 3
<a href=”/catalog/category.aspx?category=29059;page=3″ data-page=”3″>3</a>

 

There are a few options for selecting similar page elements:

  1. use a locator for each page element
  2. use a method that creates the locator for each page locator
  3. use a locator pattern with a parameter

 

The code for option 1 is below:

import static org.junit.Assert.assertTrue;
import org.junit.Test;
import org.openqa.selenium.By;
import static org.openqa.selenium.By.*;
import org.openqa.selenium.WebElement;
import static org.openqa.selenium.support.ui.ExpectedConditions.*;

public class BestBuyTest extends BaseTest {

String homePageUrl = "http://www.bestbuy.ca/";

By searchTextBoxLocator = By.id("ctl00_MasterHeader_ctl00_uchead_GlobalSearchUC_TxtSearchKeyword");

By searchButtonLocator = By.className("icon-search");

String resultsPageUrl = "http://www.bestbuy.ca/en-CA/category/";

By page2Locator = By.xpath(
"//div[@id='ctl00_CP_ctl00_ctl01_ProductSearchResultListing_topPaging']" + "//a[@data-page='2']");

By page3Locator = By.xpath(
"//div[@id='ctl00_CP_ctl00_ctl01_ProductSearchResultListing_topPaging']" +
"//a[@data-page='3']");

@Test
public void TestPaging1() throws Exception {

  openSite();
  searchBy("ipad");

  WebElement page = wait.until(
                     elementToBeClickable
                      (page2Locator));

  page.click();

  assertTrue(isPageDisplayed(2));

  page = wait.until(
          elementToBeClickable
           (page3Locator));

  page.click();

  assertTrue(isPageDisplayed(3));

}

private void openSite() {
  driver.get(homePageUrl);

  if (wait.until(
       urlToBe(homePageUrl))== false )

    throw new RuntimeException
      ("The home page is not loaded correctly!");
}

private void searchBy(String keyword) {
  WebElement searchTextBox = wait.until(
                               elementToBeClickable
                                (searchTextBoxLocator));

  searchTextBox.sendKeys("ipad");

  WebElement searchButton = wait.until(
                             elementToBeClickable
                              (searchButtonLocator));

  searchButton.click();

  if (wait.until(
       urlContains
        (resultsPageUrl))== false )

     throw new RuntimeException
     ("The results page is not loaded correctly!");
}

private boolean isPageDisplayed(int pageNumber) {
  return wait.until(
          urlContains
           ("page=" + pageNumber));
}

}

This option is not good because of the duplicated page locators.

 

Option 2 uses a method that generates the page locator:

@Test
public void TestPaging2() throws Exception {

  openSite();
  searchBy("ipad");

  WebElement page = wait.until(
                     elementToBeClickable
                      (pageLocator(2)));

  page.click();

  assertTrue(isPageDisplayed(2));

  page = wait.until(
           elementToBeClickable
            (pageLocator(3)));

  page.click();

  assertTrue(isPageDisplayed(3));

}

private By pageLocator(int page) {
String locatorText = "//div[@id='ctl00_CP_ctl00_ctl01_ProductSearchResultListing_topPaging']" +
"//a[@data-page='" +page"']";

return By.xpath(locatorText);
}

The pageLocator() method creates the locator using a page number parameter.

The locator is created by concatenating 3 parts:

  • the beginning of the xpath expression (before the page number)
  • the page number
  • the ending of the xpath expression

 

private By pageLocator(int page) {
String locatorText = "//div[@id='ctl00_CP_ctl00_ctl01_ProductSearchResultListing_topPaging']" +
"//a[@data-page='"page"']";

return By.xpath(locatorText);
}

With the pageLocator() method, there is no need of having a locator for each page element.

But the locator text was moved inside of the method.

And the method’s code is pretty complicated.

 

Option 3 is using a locator pattern with a parameter:

String pageLocatorTemplate = "//div[@id='ctl00_CP_ctl00_ctl01_ProductSearchResultListing_topPaging']" +
"//a[@data-page='%d']";

@Test
public void TestPaging3() throws Exception {

  openSite();
  searchBy("ipad");

  WebElement page = wait.until(
                     elementToBeClickable
                      (pageLocatorFromTemplate(2)));
  page.click();

  assertTrue(isPageDisplayed(2));

  page = wait.until(
          elementToBeClickable
           (pageLocatorFromTemplate(3)));

  page.click();

  assertTrue(isPageDisplayed(3));

}

private By pageLocatorFromTemplate(int page) {
  return By.xpath(
          String.format
           (pageLocatorTemplate, page));
}

We use now a locator pattern with a parameter (%d):

String pageLocatorTemplate = "//div[@id='ctl00_CP_ctl00_ctl01_ProductSearchResultListing_topPaging']" +
"//a[@data-page='%d']";

 

A method is still needed for creating the locator.

The method has a pageNumber parameter which is substituted to the parameter (%d) of the locator pattern by the String.format() method:

private By pageLocatorFromTemplate(int page) {
  return By.xpath(
          String.format
           (pageLocatorTemplate, page));
}

The 3rd solution is the best of the 3 presented.