Posts by Alex Siminiuc

me

Find child elements with locator chaining

KIT518860_broad_chain_closeup_c_wikimedia.org_[1]

Have you ever had to find an element in another element?

Of course you have.

How did you do it?

 

Maybe you used a locator that searches for the parent element first and then for the child element in the parent.

 

Like in the following test:

public class TestClass {

  private String HOME_URL = "http://www.vpl.ca/";
  private String RESULTS_URL = "vpl.bibliocommons.com/search";

  private By SEARCH_BOX_ID = By.id("edit-search");
  private By SEARCH_BUTTON_ID = By.id("edit-submit");

  private By TITLE_LOCATOR = By.xpath(
    "(//div[contains(@data-js, 'bib-list-item')])[1]" +
    "//span[@class='title']");

  //driver variable, setUp() and tearDown() methods are skipped

  @Test
  public void test1() {
    driver.get(HOME_URL);
    assertEquals(driver.getCurrentUrl(), HOME_URL);

    WebElement searchBox = driver.findElement(SEARCH_BOX_ID);
    searchBox.sendKeys("java");

    WebElement searchButton = driver.findElement(SEARCH_BUTTON_ID);
    searchButton.click();

    assertTrue(driver.getCurrentUrl().contains(RESULTS_URL));

    WebElement title = driver.findElement(TITLE_LOCATOR);
    String titleText = title.getText().toLowerCase();

    assertTrue(titleText.contains("java"));
  }

}

 

In this case, the test opens the site, searches for a keyword and then finds the title of the first result.

The locator of the title includes the locator of the first result:

private final By TITLE_LOCATOR = By.xpath(
   "(//div[contains(@data-js, 'bib-list-item')])[1]" + 
   "//span[@class='title']");

(//div[contains(@data-js, ‘bib-list-item’)])[1]  is the locator of the first result.

//span[@class=’title’] is the locator of the first result’s title.

 

This seems like a decent approach but is it not.

If the code generates an exception when trying to find the title element, is the error about the title not being available or about the first result?

Since the locator combines both, it would be difficult to know.

 

We can change the code so that the parent element is found first and stored in a WebElement variable.

 

The child element is then searched inside of the parent element:

public class TestClass {

  private String HOME_URL = "http://www.vpl.ca/";
  private String RESULTS_URL = "vpl.bibliocommons.com/search";

  private By SEARCH_BOX_ID = By.id("edit-search");
  private By SEARCH_BUTTON_ID = By.id("edit-submit");

  private By RESULT_BOX_XPATH  = By.xpath(
    "(//div[contains(@data-js, 'bib-list-item')])[1]");

  private By TITLE_XPATH = By.xpath(
    ".//span[@class='title']"); 

  //driver variable, setUp() and tearDown() methods are skipped

  @Test
  public void test2() {
    driver.get(HOME_URL);   
    assertEquals(driver.getCurrentUrl(), HOME_URL);

    WebElement searchBox = driver.findElement(SEARCH_BOX_ID);
    searchBox.sendKeys("java"); 

    WebElement searchButton = driver.findElement(SEARCH_BUTTON_ID);
    searchButton.click();

    assertTrue(driver.getCurrentUrl().contains(RESULTS_URL));

    WebElement result = driver.findElement(RESULT_BOX_XPATH);
    WebElement title = result.findElement(TITLE_XPATH);
    String titleText = title.getText().toLowerCase();

    assertTrue(titleText.contains("java"));
  }
}

 

This time, the code finds the result element and stores it in a WebElement variable:

WebElement result = driver.findElement(RESULT_BOX_XPATH);

Then, it searches for the title element inside of the result element:

WebElement title = result.findElement(TITLE_INNER_XPATH);

Notice that the title locator starts with a dot.

TITLE_XPATH = By.xpath(".//span[@class='title']");

This is important when searching an element in another element.

When the code works this way, in case of an error, it is very clear which element caused it.

 

This is better than before but it extends poorly.

What if you need to find an element included in another element included in another element included in another element?

For each parent element, you need to search for the element and store it in a variable.

Which leads to lots of duplication.

This duplication can be removed using the ByChained Selenium class.

See how the code looks now:

public class TestClass {

  private String HOME_URL = "http://www.vpl.ca/";
  private String RESULTS_URL = "vpl.bibliocommons.com/search";

  private By SEARCH_BOX_ID = By.id("edit-search");
  private By SEARCH_BUTTON_ID = By.id("edit-submit");

  private By RESULT_BOX_XPATH  = By.xpath(
    "(//div[contains(@data-js, 'bib-list-item')])[1]");

  //driver variable, setUp() and tearDown() methods are skipped

  private By TITLE_XPATH = By.xpath(".//span[@class='title']");

  @Test
  public void test3() {
    driver.get(HOME_URL);    
    assertEquals(driver.getCurrentUrl(), HOME_URL);

    WebElement searchBox = driver.findElement(SEARCH_BOX_ID);
    searchBox.sendKeys("java");  

    WebElement searchButton = driver.findElement(SEARCH_BUTTON_ID);
    searchButton.click();

    assertTrue(driver.getCurrentUrl().contains(RESULTS_URL));

    WebElement title = driver.findElement(
         new ByChained(RESULT_BOX_XPATH,
                TITLE_XPATH));

    String titleText = title.getText().toLowerCase();

    assertTrue(titleText.contains("java"));
  }  
}

The code to find the title looks rather weird:

WebElement title = driver.findElement(
         new ByChained(RESULT_BOX_XPATH,TITLE_XPATH));

It creates a new object for the ByChained class.

The object gets 2 parameters in the constructor for the locators of the parent and child elements.

Finding the element happens as follows:

  • the element for the 1st locator is searched
  • the element for the 2nd locator is searched in the first element

Using this approach scales well even if we have elements included in many other elements.

There are no additional WebElements that should be created and the code stays approximately the same size.

 


Tips and tricks are great.

Keep coming to this blog for more.

However, if you are interested in better and more detailed learning, this Kindle ebook will help you improve your Selenium code:

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

I am using all these patterns daily and so can you.

Advertisements

Why do static variables and methods suck?

I believe they are very full at times.

I got this comment on a LinkedIn post for the differences between Selenium testers and Selenium developers.

It is worth providing an answer.

Now, I am not a Java expert, far from it.

But others are.

For example, Simon Stewart.

Yes, that Simon Stewart who created Seleniumn WebDriver.

This is from his blog:

“Singletons? Static Methods? Also No.
Singletons (in the traditional “implemented as a static field in a class” sense, not in the “ideally we’d only have one of these” sense) destroy our ability to have fun and write tests that can run in parallel, slashing our potential productivity. Also, it leads people to start using the Service Locator pattern instead of Dependency Injection, and we take DI as an article of faith (see above), mainly because it facilitates TDD by making collaborators clear, like we (also) said above.”

So, static methods? Also no.

Do you need more reasons against static variables and methods?

They promote code that is not object oriented.

 

Object oriented code is about objects.

 

Every method is used on an object.

 

Since static variables or methods are for the class but not the class’s objects, you are writing code that does not use objects.

 

Have a look at the following articles for more details:

Still not convinced?

Read this topic from Stack Overflow.

 

Still in doubt?

Continue to use them.

What are the differences between Selenium testers and Selenium developers?

wir[1]

What follows is inspired from reality.

It is not a product of my imagination.

 

Tester: Selenium is a tool like QTP.

Developer: Selenium is a Java library for web test automation.

 

Tester: I will automate test cases by recording them with Selenium IDE.
Developer: Selenium IDE is not an automation tool.

 

Tester: I dont need to write code like developers. I used to write Visual Basic code and that should be enough for automation.
Developer: The automation code should be at the level of production application code.

 

Tester: Every time my test needs a verification, I do it in IF/ELSE statements.
Developer: Verifications are done with JUNIT or TESTNG assertions.

 

Tester: Who cares if all code is in the test method? It works and it looks good to me.
Developer: I will first write an automation framework.
Then I will create the page object classes that are used in the test methods.

 

Tester: Explicit waits and expected conditions are not reliable and dont work well for me. I have created my own.
Developer: Explicit waits and expected conditions work well. I extend them in custom classes when needed.

 

Tester: Look, my test works! That is all I care about! I can start a new test!
Developer: My test should work but also be easy to understand, short and maintainable.

 

Tester: All my libraries are imported to the project. If I need new versions, I remove the old libraries and import the new ones. I used Maven long time ago and dont remember much about it.
Developer: I use Maven for managing dependencies.

 

Tester: All tests run from my computer every night. I run them anytime someone needs them.
Developer: Anyone can run my tests from Jenkins. They are scheduled to run every night.

 

Tester: My tests run in parallel on my computer.
Developer: My tests run in parallel on all available Jenkins slaves.

 

Tester: I love using static methods and static variables.
Developer: Static sucks. I avoided it as much as possible.

 

Tester: My project is very cool. I have a utility class for all common purpose methods. All page classes inherit from the utility class. This makes my page classes shorter.
Developer: I use composition to break the page classes in page element classes.

 

Tester: All my page element locators are in property text files.
Developer: If the page element locators are outside of the page classes, the class encapsulation is broken.

 

Do you have anything else that should be added to the list?

Please tell in the comments so I can add more.

 


 

Want to be more than a Selenium tester?

Start with this Kindle book on automation patterns that can make your Selenium code better:

Improve Selenium Code with Automation Patterns:

Page Object Model, Page Factory, Page Elements, Base Page, Loadable Component

 

amazon book

 

 

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.