Extend, Dont Change

Keep-calm-and-don-t-change-7

The test automation code should be as professional as the application code.

It should be built using the same principles.

Such as “program to an interface” and “extend, dont change”.

 

How do you build it so that it follows such principles?

Let’s imagine that we have a class that formats a phone number with spaces between the area code, prefix and the line number.

For a phone number like 6143567891, the PhoneNumber class displays it as 614 356 7891.

import java.util.ArrayList;
import java.util.List;

public class PhoneNumber {
 
  private List<Integer> digits = new ArrayList<>(); 

  public PhoneNumber(List<Integer> digits) { 
    if (digits.size() != 10) 
      throw new RuntimeException("there are less than 10 digits!"); 

    this.digits = digits; 
  }

  public List<Integer> getDigits() { 
    return this.digits; 
  } 

  public String getValue() { 
    return String.format("%s %s %s", 
                         areaCode(), 
                         prefix(), 
                         lineNumber()); 
  } 

  public String areaCode() { 
    return String.format("%d%d%d", 
                         digits.get(0), 
                         digits.get(1), 
                         digits.get(2)); 
  } 

  public String prefix() { 
    return String.format("%d%d%d",   
                         digits.get(3), 
                         digits.get(4), 
                         digits.get(5)); 
  }

  public String lineNumber() { 
    return String.format("%d%d%d%d",   
                         digits.get(6), 
                         digits.get(7), 
                         digits.get(8), 
                         digits.get(9)); 
  } 

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

}

The following test displays a phone number formatted properly:

import java.util.Arrays;
import java.util.List;
import org.testng.annotations.Test;

public class TestClass {
  private final static List<Integer> TEN_DIGITS = 
          Arrays.asList(4, 6, 1, 2, 3, 2, 6, 7, 4, 5); 

  @Test 
  public void testNormalPhone() { 
    PhoneNumber phone = new PhoneNumber(TEN_DIGITS); 
    System.out.println(phone.getValue()); 
  } 
}

 

For a while, this phone template is the only template needed for the project.

Multiple classes use objects of the PhoneNumber class.

But things change and various other formats become useful such as:

(614) 356 7891 : the area code is in ()

614-356-7891 : the area code, prefix and line number are separated by –

604 356 7891 : the 604 prefix is used

778 356 7891 : the 778 prefix is used

 

How can the additional templates be implemented?

You may choose to make changes to the PhoneNumber class for the additional templates.

Since there is already a getValue() method used in many other classes, you leave it as is and create additional methods, one for each additional template:

import java.util.ArrayList;
import java.util.List;
 public class PhoneNumber { 

  private List<Integer> digits = new ArrayList<>(); 
  
  public PhoneNumber(List<Integer> digits) { 
    if (digits.size() != 10) 
      throw new RuntimeException("there are less than 10 digits!"); 

    this.digits = digits; 
  } 

  public List<Integer> getDigits() { 
    return this.digits; 
  } 

  public String getValue() { 
    return String.format("%s %s %s", 
                         areaCode(), 
                         prefix(), 
                         lineNumber()); 
  }
  public String getValueWithParanthesis() { 
   return String.format("(%s) %s %s", 
                        areaCode(), 
                        prefix(), 
                        lineNumber()); 
  } 

  public String getValueWithHyphens() { 
    return String.format("%s-%s-%s", 
                         areaCode(), 
                         prefix(), 
                         lineNumber()); 
  } 

  public String getValueWith604Prefix() { 
    return String.format("604 %s %s", 
                         prefix(), 
                         lineNumber()); 
  }
  public String getValueWith778Prefix() { 
    return String.format("778 %s %s", 
                         prefix(), 
                         lineNumber()); 
  } 

  public String areaCode() { 
    return String.format("%d%d%d", 
                         digits.get(0), 
                         digits.get(1), 
                         digits.get(2)); 
  } 

  public String prefix() { 
    return String.format("%d%d%d", 
                         digits.get(3), 
                         digits.get(4), 
                         digits.get(5)); 
  } 

  public String lineNumber() { 
    return String.format("%d%d%d%d", 
                        digits.get(6), 
                        digits.get(7), 
                        digits.get(8),  
                        digits.get(9)); 
  }
 
  @Override 
  public String toString() { 
    return this.digits.toString(); 
  } 
}

 

The test displays all possible formats:

import java.util.Arrays;
import java.util.List; import org.testng.annotations.Test; 
public class TestClass {
  private final static List<Integer> TEN_DIGITS = 
                Arrays.asList(4, 6, 1, 2, 3, 2, 6, 7, 4, 5); 

  @Test 
  public void testPhoneTemplates() { 

    PhoneNumber phone1 = new PhoneNumber(TEN_DIGITS); 
    System.out.println(phone1.getValue()); 

    PhoneNumber phone2 = new PhoneNumber(TEN_DIGITS); 
    System.out.println(phone2.getValueWithParanthesis()); 

    PhoneNumber phone3 = new PhoneNumber(TEN_DIGITS); 
    System.out.println(phone3.getValueWithHyphens()); 

    PhoneNumber phone4 = new PhoneNumber(TEN_DIGITS); 
    System.out.println(phone4.getValueWith604Prefix()); 

    PhoneNumber phone5 = new PhoneNumber(TEN_DIGITS); 
    System.out.println(phone5.getValueWith778Prefix()); 

  } 
}

 

This works.

But we just violated many principles that should be followed when writing code:

  • program to an interface

When creating a phone number object, it should be saved in a variable that uses an interface as type:

IPhoneNumber phoneNumber = new PhoneNumber();

Instead, we save the phone number object in a variable with the PhoneNumber type:

PhoneNumber phoneNumber = new PhoneNumber();
  • extend, not change

We changed the PhoneNumber class by adding 4 additional methods.

A class should not be changed since this may create issues in all other classes that use its objects.

  • single responsibility

The PhoneNumber class started with having one responsibility, to format a phone number.

Now, the class has 5 responsibilities by providing 5 types of formatting.

 


 

What do we do?

How do we change the code so all these 3 principles are followed?

 


 

  1. Create a IPhoneNumber interface

import java.util.List;
 public interface IPhoneNumber { 

   public String getValue(); 

   public List<Integer> getDigits(); 

   public String areaCode(); 

   public String prefix();
   public String lineNumber(); 

}

 

2. Create a class for each phone number template and  implement the IPhoneNumber interface

 

PhoneNumber class

import java.util.ArrayList;
import java.util.List; 
public class PhoneNumber implements IPhoneNumber { 

  private final String phoneTemplate = "%s %s %s"; 

  private List<Integer> digits = new ArrayList<>(); 

  public PhoneNumber(List<Integer> digits) { 
    if (digits.size() != 10) 
      throw new RuntimeException("there are less than 10 digits!"); 

    this.digits = digits; 
  } 
  @Override
  public String getValue() { 
    return String.format(phoneTemplate,               
                         areaCode(),  
                         prefix(), 
                         lineNumber()  ); 
  } 

  @Override
  public List<Integer> getDigits() { 
    return this.digits; 
  } 

  @Override
  public String areaCode() { 
    return String.format("%d%d%d",               
                         digits.get(0), 
                         digits.get(1),  
                         digits.get(2)); 
  } 

  @Override
  public String prefix() { 
    return String.format("%d%d%d",               
                         digits.get(3), 
                         digits.get(4), 
                         digits.get(5)); 
  } 

  @Override
  public String lineNumber() { 
    return String.format("%d%d%d%d",               
                         digits.get(6), 
                         digits.get(7),
                         digits.get(8),
                         digits.get(9)); 
  } 

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

}

 

PhoneNumberWithParanthesis class

import java.util.ArrayList;
import java.util.List; 
public class PhoneNumberWithParanthesis implements IPhoneNumber { 

  private final String phoneTemplate = "(%s) %s %s"; 
  private List<Integer> digits = new ArrayList<>(); 

  public PhoneNumberWithParanthesis(List<Integer> digits) { 
    if (digits.size() != 10) 
      throw new RuntimeException("there are less than 10 digits!"); 

    this.digits = digits; 
  } 
  public String getValue() { 
    return String.format(phoneTemplate,               
                         areaCode(),  
                         prefix(), 
                         lineNumber()); 
  } 

  public List<Integer> getDigits() { 
    return this.digits; 
  } 

  public String areaCode() { 
    return String.format("%d%d%d",               
                         digits.get(0),               
                         digits.get(1),               
                         digits.get(2)); 
  } 

  public String prefix() { 
    return String.format("%d%d%d",               
                         digits.get(3),               
                         digits.get(4),               
                         digits.get(5)); 
  } 

  public String lineNumber() { 
    return String.format("%d%d%d%d",               
                         digits.get(6),               
                         digits.get(7),               
                         digits.get(8),              
                         digits.get(9)); 
  } 

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

}

 

PhoneNumberWithHyphens class

import java.util.ArrayList;
import java.util.List; 
public class PhoneNumberWithHyphens implements IPhoneNumber { 

  private final String phoneTemplate = "%s-%s-%s"; 
  private List<Integer> digits = new ArrayList<>(); 

  public PhoneNumberWithHyphens(List<Integer> digits) { 
    if (digits.size() != 10) 
      throw new RuntimeException("there are less than 10 digits!"); 

    this.digits = digits; 
  } 
  @Override
  public String getValue() { 
    return String.format(phoneTemplate,               
                         areaCode(),  
                         prefix(), 
                         lineNumber()); 
  } 

  @Override
  public List<Integer> getDigits() { 
    return this.digits; 
  } 

  @Override
  public String areaCode() { 
    return String.format("%d%d%d",               
                         digits.get(0),               
                         digits.get(1),               
                         digits.get(2)); 
  } 

  @Override
  public String prefix() { 
    return String.format("%d%d%d",               
                         digits.get(3),               
                         digits.get(4),               
                         digits.get(5)); 
  } 

  @Override
  public String lineNumber() { 
    return String.format("%d%d%d%d",               
                         digits.get(6),               
                         digits.get(7),               
                         digits.get(8),              
                         digits.get(9)); 
  } 

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

 

PhoneNumberStartingWith604 class

import java.util.ArrayList;
import java.util.List;
 public class PhoneNumberStartingWith604 implements IPhoneNumber { 

  private final String phoneTemplate = "%s %s %s"; 
  private List<Integer> digits = new ArrayList<>(); 

  public PhoneNumberStartingWith604(List<Integer> digits) { 

    if (digits.size() != 10) 
      throw new RuntimeException("there are less than 10 digits!"); 
  
    this.digits = digits; 
  }

  @Override   public String getValue() { 
    return String.format(phoneTemplate,               
                         areaCode(),  
                         prefix(), 
                         lineNumber()); 
  } 

  @Override
  public List<Integer> getDigits() { 
    return this.digits; 
  } 

  @Override
  public String areaCode() { 
    return "604"; 
  } 

  @Override
  public String prefix() { 
    return String.format("%d%d%d",               
                         digits.get(3),               
                         digits.get(4),               
                         digits.get(5)); 
  } 

  @Override
  public String lineNumber() { 
    return String.format("%d%d%d%d",               
                         digits.get(6),               
                         digits.get(7),               
                         digits.get(8),              
                         digits.get(9)); 
  } 

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

}

 

PhoneNumberStartingWith778 class

import java.util.ArrayList;
import java.util.List;
 public class PhoneNumberStartingWith778 implements IPhoneNumber { 

  private final String phoneTemplate = "%s %s %s"; 
  private List<Integer> digits = new ArrayList<>(); 

  public PhoneNumberStartingWith778(List<Integer> digits) { 
    if (digits.size() != 10) 
      throw new RuntimeException("there are less than 10 digits!"); 

    this.digits = digits; 
  } 
  @Override
  public String getValue() { 
    return String.format(phoneTemplate,               
                         areaCode(),  
                         prefix(), 
                         lineNumber()); 
  } 

  @Override
  public List<Integer> getDigits() { 
    return this.digits; 
  } 

  @Override
  public String areaCode() { 
    return "778"; 
  } 

  @Override
  public String prefix() { 
    return String.format("%d%d%d",               
                         digits.get(3),               
                         digits.get(4),               
                         digits.get(5)); 
  } 

  @Override
  public String lineNumber() { 
    return String.format("%d%d%d%d",               
                         digits.get(6),               
                         digits.get(7),               
                         digits.get(8),              
                         digits.get(9)); 
  } 

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

}

 

The test displays all possible templates for a phone number:

import java.util.Arrays;
import java.util.List; import org.testng.annotations.Test; 
public class TestClass {
  private final static List<Integer> TEN_DIGITS = 
              Arrays.asList(4, 6, 1, 2, 3, 2, 6, 7, 4, 5); 

  @Test 
  public void testPhoneTemplates() { 

    IPhoneNumber phone1 = new PhoneNumber(TEN_DIGITS); 
    System.out.println(phone1.getValue()); 

    IPhoneNumber phone2 = new PhoneNumberWithParanthesis(TEN_DIGITS); 
    System.out.println(phone2.getValue()); 

    IPhoneNumber phone3 = new PhoneNumberWithHyphens(TEN_DIGITS); 
    System.out.println(phone3.getValue()); 

    IPhoneNumber phone4 = new PhoneNumberStartingWith604(TEN_DIGITS); 
    System.out.println(phone4.getValue()); 

    IPhoneNumber phone5 = new PhoneNumberStartingWith778(TEN_DIGITS); 
    System.out.println(phone5.getValue()); 
  } 
}

 

The code is better because

  • each class has one responsibility only (for a single phone template)
  • each class implements an interface; each phone object, even if created with a class, is saved in a variable with the IPhoneNumber interface

 

But, looking at all classes, it is easy to see that have so much duplicated code.

 


 

What do we do next?

A different approach is needed.

We started with a PhoneNumber class that formats a phone number using a template.

We would like not to modify this class but extend it so that we can format the phone number with other templates.

Decorators provide this behaviour.

A decorator allows extending the functionality of a class without making any changes to the class.

We need a few steps for the new implementation:

  1. We keep the same IPhoneNumber interface

import java.util.List; 
public interface IPhoneNumber { 

  public String getValue(); 

  public List<Integer> getDigits(); 

  public String areaCode(); 

  public String prefix(); 

  public String lineNumber(); 
}

 

2. We keep the PhoneNumber class in its original shape.

The PhoneNumber class implements the IPhoneNumber interface:

import java.util.ArrayList;
import java.util.List; 
public class PhoneNumber implements IPhoneNumber { 

  private final String phoneTemplate = "%s %s %s"; 
  private List<Integer> digits = new ArrayList<>(); 

  public PhoneNumber(List<Integer> digits) { 
    if (digits.size() != 10) 
      throw new RuntimeException("there are less than 10 digits!"); 

    this.digits = digits; 
  } 

  @Override
  public List<Integer> getDigits() { 
    return this.digits; 
  } 

  @Override
  public String getValue() { 
    return String.format(phoneTemplate,  
                         areaCode(),  
                         prefix(), 
                         lineNumber()); 
  } 
  @Override
  public String areaCode() { 
    return String.format("%d%d%d",               
                         digits.get(0),               
                         digits.get(1),               
                         digits.get(2)); 
  } 

  @Override 
  public String prefix() { 
    return String.format("%d%d%d",               
                         digits.get(3),               
                         digits.get(4),               
                         digits.get(5)); 
  } 

  @Override 
  public String lineNumber() { 
    return String.format("%d%d%d%d",               
                         digits.get(6),               
                         digits.get(7),               
                         digits.get(8),              
                         digits.get(9)); 
  } 

}

 

3.  Create an abstract class named PhoneDecorator:

import java.util.List; 
public abstract class PhoneDecorator implements IPhoneNumber { 
  private IPhoneNumber decoratedPhone; 

  public PhoneDecorator(IPhoneNumber phone) { 
    decoratedPhone = phone; 
  } 

  public String getValue() { 
    return decoratedPhone.getValue(); 
  } 

  public List<Integer> getDigits() { 
    return decoratedPhone.getDigits(); 
  } 

  public String areaCode() { 
    return decoratedPhone.areaCode(); 
  } 

  public String prefix() { 
    return decoratedPhone.prefix(); 
  } 

  public String lineNumber() { 
    return decoratedPhone.lineNumber(); 
  }

}

The abstract class PhoneDecorator will be used as parent for all specific decorator classes.

We will have one decorator class for each new template:

  • Phone With Paranthesis
  • Phone With Hyphens
  • Phone With 604 Prefix
  • Phone With 778 Prefix

 

4. Create the specific decorators

 

PhoneWithParanthesisDecorator class

public class PhoneWithParanthesisDecorator extends PhoneDecorator {    public PhoneWithParanthesisDecorator(IPhoneNumber phone) {      super(phone);    }    public String getValue() {      String phoneTemplate = "(%s) %s %s";      return String.format(phoneTemplate,                                         super.areaCode(),                           super.prefix(),                                        super.lineNumber());    } }

 

PhoneWithHyphensDecorator class

public class PhoneWithHyphensDecorator extends PhoneDecorator { 
  
  public PhoneWithHyphensDecorator(IPhoneNumber phone) { 
    super(phone); 
  } 

  public String getValue() { 
    String phoneTemplate = "%s-%s-%s"; 
    
    return String.format(phoneTemplate,               
                         super.areaCode(),  
                         super.prefix(),   
                         super.lineNumber()); 
  } 

}

 

PhoneWith604PrefixDecorator class

public class PhoneWith604PrefixDecorator extends PhoneDecorator { 

  public PhoneWith604PrefixDecorator(IPhoneNumber phone) { 
    super(phone); 
  } 

  public String getValue() { 
    String phoneTemplate = "%s %s %s"; 

    return String.format(phoneTemplate,               
                         "604", 
                         super.prefix(),   
                         super.lineNumber()); 
  }

}

 

PhoneWith778PrefixDecorator class

public class PhoneWith778PrefixDecorator extends PhoneDecorator { 

  public PhoneWith778PrefixDecorator(IPhoneNumber phone) { 
    super(phone); 
  } 

  public String getValue() { 
    String phoneTemplate = "%s %s %s"; 

    return String.format(phoneTemplate, 
                         "778",  
                         super.prefix(),  
                         super.lineNumber()); 
  }

}

 

Each decorator only implements the change about a specific template.

It does this by encapsulating an IPhoneNumber object and then overriding the getValue() method of the PhoneDecorator class.

There is no code duplication between the phone decorator classes.

They do not have code for anything else than the getValue() method.

 

5. Wrap the PhoneNumber objects with specific decorators to use other phone templates

The test method puts everything together:

import java.util.Arrays;import java.util.List; import org.testng.annotations.Test; 
public class TestClass {
   private final static List<Integer> TEN_DIGITS = 
          Arrays.asList(4, 6, 1, 2, 3, 2, 6, 7, 4, 5); 

  @Test 
  public void testPhoneTemplates() { 

    IPhoneNumber phone1 = new PhoneNumber(TEN_DIGITS); 
    System.out.println(phone1.getValue()); 

    IPhoneNumber phone2 = new PhoneWithParanthesisDecorator( 
                             new PhoneNumber(TEN_DIGITS)); 
    System.out.println(phone2.getValue()); 

    IPhoneNumber phone3 = new PhoneWithHyphensDecorator(
                             new PhoneNumber(TEN_DIGITS)); 
    System.out.println(phone3.getValue()); 

    IPhoneNumber phone4 = new PhoneWith604PrefixDecorator(
                             new PhoneNumber(TEN_DIGITS)); 
    System.out.println(phone4.getValue()); 

    IPhoneNumber phone5 = new PhoneWith778PrefixDecorator(
                             new PhoneNumber(TEN_DIGITS)); 
    System.out.println(phone5.getValue()); 

  }

}

 


 

What did we get when using decorators?

  1. No changes are made to the original class
  2. We program to an interface; all phone number objects are saved in IPhoneNumber variables
  3. Every time we need a different phone template than the one implemented in the PhoneNumber class, we create a decorator for it.
  4. To use the new template, we “decorate” the PhoneNumber object by wrapping it with a decorator object

 

 

 

 

 

 

 

 

Advertisement

Selenium Tips and Tricks

tipsandtricks

I post once in a while tips and tricks for Selenium and Java on Quora.

This is the list of the tips published so far:

Use a detailed error message with assertTrue

Do not chain methods of different classes

 

 

What is the best approach to understand how an existing Selenium automation framework works?

top-down

This is how I did it in 2016.

My automation project needed Page Factory and also classes for each type of HTML element (TextInput class, Link class, Button class, Image class, List class, etc).

Someone mentioned this framework as very useful.

It seemed to do everything that I was interested in so I started to evaluate it.

The evaluation approach was top-down, starting from the top layer, and going down layer by layer:

  1. create the Maven project and compile it
  2. build a few tests and the page classes that they need
  3. figure out how the page object and page element classes use framework components
  4. figure out how the framework components work individually
  5. figure out how the framework components work together

1. Compile the Maven project

I donwloaded the source code from Github and set up a Maven project with it.

Then, I removed any unneeded dependencies from pom.xml and updated the versions for the remaining ones:

< modelVersion>4.0.0</ modelVersion>

< parent>
   < groupId>ru.yandex.qatools.htmlelements</ groupId>
   < artifactId>htmlelements</ artifactId>
   < version>1.19-SNAPSHOT</ version>
< /parent>

< artifactId>htmlelements-java</ artifactId>
< name>Yandex QATools HtmlElements For Java</ name>

< properties>
  < selenium.version>3.5.1</ selenium.version>        
</ properties>

< dependencies>
   < dependency>
      < groupId>org.seleniumhq.selenium</ groupId>
      < artifactId>selenium-java</ artifactId>
      < version>${selenium.version}</ version>
   </ dependency>
		
   < dependency>
      < groupId>org.apache.commons</ groupId>
      < artifactId>commons-lang3</ artifactId>
      < version>3.4</ version>
   </ dependency>
		
   < dependency>
      < groupId>junit</ groupId>
      < artifactId>junit</ artifactId>
      < version>4.12</ version>
      < scope>test</ scope>
   </ dependency>

   < dependency>
      < groupId>org.hamcrest</ groupId>
      < artifactId>hamcrest-all</ artifactId>
      < version>1.3</ version>
      < scope>test</ scope>
   </ dependency>

</ dependencies>

This is the cleaned up pom.xml.

I removed the following dependencies from it:

  • phantomJS (not interested in running tests in a headless browser)
  • mockito (no need of any mocking)

Then, I compiled the code using mvn compile and resolved any compilation errors.

When there were no more compilation errors, I moved on to phase 2.

2. Build a few tests and the page classes that they need

I am not going to show you the tests and page classes that I created as they are not relevant.  Lets just say that they are similar with the examples from the framework’s github page.

So this is the test class:

public class SampleTest {
  private WebDriver driver = new FirefoxDriver();    
  private SearchPage searchPage = new SearchPage(driver);

  @Before
  public void loadPage() {        
    driver.get("http://www.yandex.com");    
  }
  @Test    
  public void sampleTest() {        
    searchPage.search("yandex");        
    // Some assertion here    
  }
  @After    
  public void closeDriver() {        
    driver.quit();    
  }
}

The test uses an object of the SearchPage page class and interacts with it through its methods.

The SearchPage class is very simple:

public class SearchPage {
  private SearchArrow searchArrow;    

  // Other blocks and elements here ...
  public SearchPage(WebDriver driver) {        
    PageFactory.initElements(
        new HtmlElementDecorator(
          new HtmlElementLocatorFactory(driver)), this);    
  }
  public void search(String request) {        
    searchArrow.search(request);    }
    // Other methods here ...
  }
}

The search() method uses a searchArror class field for executing a search.

What about the SearchArrow class?

Here it is:

@Name("Search form")
@FindBy(xpath = "//form")
public class SearchArrow extends HtmlElement {
  @Name("Search request input")    
  @FindBy(id = "searchInput")    
  private TextInput requestInput;

  @Name("Search button")    
  @FindBy(className = "b-form-button__input")    
  private Button searchButton;

  public void search(String request) {        
    requestInput.sendKeys(request);        
    searchButton.click();    
  }
}

It does not matter at this point how anything works.

The point is to make sure that you can do something with it.

After you are sure that it can be used, keep looking into how it works.

 

3. Figure out how the page and elements classes use framework components

SearchPage class is interesting in a few different ways:

  • it does not declare any web element fields and annotations (like a standard page factory implementation)
  • it declares a searchArrow field for the SearchArrow class which is then initialized in the class’s constructor; the standard page factory implementation does this only for web elements
public class SearchPage {
  private SearchArrow searchArrow;    

  // Other blocks and elements here ...
  
  public SearchPage(WebDriver driver) {        
    PageFactory.initElements(
      new HtmlElementDecorator(
        new HtmlElementLocatorFactory(driver)), this);    }

 

The SearchArrow class is interesting as well.

The search() method uses 2 elements, requestInput and Button.

They dont use the WebElement type but new ones (TextInput and Button).

They still have @FindBy annotations but also @Name annotations (which are probably new).

The @FindBy and @Name annotations are used not only by the elements of the SearchArrow class but also by the class itself.

What framework components are obvious so far?

The framework seems to define a class for each type of HTML element (TextInput, Button).

The @FindBy annotation can be used not only for elements but also for the classes.

The @FindBy annotation can be used for objects with the TextInput and Button classes.

The PageFactory.initElements() can initialize TextInput and Button elements.

 

4. Figure out how the framework components work individually

First the TextInput and Button classes.

They are in the following package:

https://github.com/yandex-qatools/htmlelements/tree/master/htmlelements-java/src/main/java/ru/yandex/qatools/htmlelements/element

As suspected, there is a class for each type of html element:

  1. Button.java
  2. CheckBox.java
  3. FileInput.java
  4. Form.java
  5. HtmlElement.java
  6. Image.java
  7. Link.java
  8. Named.java
  9. Radio.java
  10. Select.java
  11. Table.java
  12. TextBlock.java
  13. TextInput.java
  14. TypifiedElement.java

 

This is the TextInput class:

package ru.yandex.qatools.htmlelements.element;
import org.apache.commons.lang3.StringUtils;
import org.openqa.selenium.Keys;import org.openqa.selenium.WebElement;
import java.util.Optional;

public class TextInput extends TypifiedElement {

  public TextInput(WebElement wrappedElement) {        
    super(wrappedElement);    
  }

  @Deprecated    
  public String getEnteredText() {        
    return getText();    
  }

  @Override    
  public String getText() {        
    if ("textarea".equals(getWrappedElement().getTagName())) {            
      return getWrappedElement().getText();        
    }
    return Optional.ofNullable(
           getWrappedElement().getAttribute("value")).orElse("");    
  }
  public String getClearCharSequence() {        
    return StringUtils.repeat(
      Keys.DELETE.toString() + Keys.BACK_SPACE, getText().length());    
  }

}

 

TextInput extends TypifiedElement so I should look into that class as well.

Because of the inheritance, TextInput can use all protected/public methods of TypifiedElement.

In addition, it defines methods for getting text and clearing value.

One thing to notice is how TextInput’s constructor gets a WebElement as a parameter. There is no driver or By locator passed as parameter.

TypifiedElement is below:

package ru.yandex.qatools.htmlelements.element;

import org.openqa.selenium.By;
import org.openqa.selenium.Dimension;
import org.openqa.selenium.NoSuchElementException;
import org.openqa.selenium.OutputType;
import org.openqa.selenium.Point;
import org.openqa.selenium.Rectangle;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.internal.WrapsElement;
import java.util.List;

public abstract class TypifiedElement 
   implements WrapsElement, Named, WebElement {    

  private final WebElement wrappedElement;    
  private String name;

  protected TypifiedElement(WebElement wrappedElement) {        
    this.wrappedElement = wrappedElement;    
  }
  
  @Override    
  public String getName() {        
    return name;    
  }
  public void setName(String name) {        
    this.name = name;    
  }
  @Override    
  public String toString() {        
    return name;    
  }

  public boolean exists() {        
    try {            
      getWrappedElement().isDisplayed();        
    } 
    catch (NoSuchElementException ignored) {            
      return false;        
    }         

    return true;    
  }
  @Override    
  public WebElement getWrappedElement() {        
    return wrappedElement;    
  }
  @Override    
  public void click() {        
    getWrappedElement().click();    
  }
  @Override    
  public void submit() {        
    getWrappedElement().submit();    
  }
  @Override    
  public void sendKeys(CharSequence... keysToSend) {        
    getWrappedElement().sendKeys(keysToSend);    
  }

  @Override    
  public void clear() {        
    getWrappedElement().clear();    
  }

  @Override    
  public String getTagName() {        
    return getWrappedElement().getTagName();    
  }
  @Override    
  public String getAttribute(String name) {        
    return getWrappedElement().getAttribute(name);    
  }
  @Override    
  public boolean isSelected() {        
    return getWrappedElement().isSelected();    
  }

  @Override    
  public boolean isEnabled() {        
    return getWrappedElement().isEnabled();    
  }
  @Override    
  public String getText() {        
    return getWrappedElement().getText();    
  }
  @Override    
  public List findElements(By by) {        
    return getWrappedElement().findElements(by);    
  }
  
  @Override    
  public WebElement findElement(By by) {        
    return getWrappedElement().findElement(by);    
  }
  
  @Override    
  public boolean isDisplayed() {        
    return getWrappedElement().isDisplayed();    
  }

  @Override    
  public Point getLocation() {        
    return getWrappedElement().getLocation();    
  }

  @Override    
  public Dimension getSize() {        
    return getWrappedElement().getSize();    
  }

  @Override    
  public Rectangle getRect() {        
    return getWrappedElement().getRect();    
  }
  
  @Override    
  public String getCssValue(String propertyName) {        
    return getWrappedElement().getCssValue(propertyName);    
  }
  
  @Override    
  public X getScreenshotAs(OutputType target) {        
    return getWrappedElement().getScreenshotAs(target);    
  }

}

 

TypifiedElement is an abstract class which is normal since it is used as parent for the html classes.

It implements the WebDriver interface and a few other which again makes sense since you would like to be able to do something like:

WebElement textBox = new TextInput();
WebElement button = new Button();

 

All these in the name of the “programming to an interface” principle.

So things are a bit more clear about the html classes.

What else is there?

The framework has a few other packages:

  • annotations (where the Name and Timeout annotations are defined)
  • element (we looked at its classes already, TextInput, Button, TypifiedElement)
  • exceptions (you will find a custom exceptions class in here)
  • utils (various html element utility methods)
  • loader
  • page factory

 

The next thing to figure out is how exactly are the html element initialized.

So, lets have a look in the page factory package.

It only has 1 interface:

package ru.yandex.qatools.htmlelements.pagefactory;

import org.openqa.selenium.support.pagefactory.ElementLocator;
import org.openqa.selenium.support.pagefactory.ElementLocatorFactory;

public interface CustomElementLocatorFactory 
  extends ElementLocatorFactory {  
  
  ElementLocator.createLocator(Class clazz);

}

 

The secrets must be then in the loader package.

I am going to stop here because explaining how page factory works would need a book instead of an article.

In Summary,

There is only one way of understanding how a framework works: you have to read the code.

Use a top-down approach and go in the project layer after layer.

Start with the tests, then look at the page class, then page element classes, then the framework components.

Sometimes, you may get stuck because you don’t understand how different things work. Such as generics, reflection, proxy classes, annotations, exceptions, etc.

No problem, put the framework discovery on hold and learn the new topic well.
Then resume reading about your new framework.

Before you go

Was this article useful to you?

If yes, I am glad to help.

Can you help me as well?

 

6 things to avoid when learning selenium automation

dontdoit-1

1. Aiming at becoming a Selenium automation expert

This goal is so high that you will probably fail.

You should replace it with something more achievable like “i want to get a test automation job in 1 year”.

 

2. Selenium WebDriver certifications

Selenium WebDriver certifications are scams. Even if they would be good, they wont help you getting hired. When studying for a certification, you study for the exam that you will take and not for the subject.

Passing an exam is a very poor way of measuring your programming skills.

 

I met people who passed with 90% grades TOEFL (test of english as a foreign language) and TWE (test of written english) but speak and write very bad english.

 

3. Jumping into writing Selenium WebDriver tests before learning a programming language

Test automation with Selenium WebDriver is basically programming.

Without knowing a programming language well, you will always have big obstacles in writing automation code. Selenium WebDriver is just a library that can be used through multiple languages.

Jumping to Selenium tests with no programming is similar with trying to cook lasagna with no cooking skills.

Shortcuts never work. There are no shortcuts to math, to learning french, to getting fit, to anything. You have to put the effort in and follow a process that takes time.

 

4. Looking for the best selenium book or online course

There is no such thing. You will find though many great books and online courses.

The best book for Selenium should include everything about Selenium, everything about the programming language (lets say Java), everything about an IDE (example: Eclipse), everything about unit testing, maven, code design, code refactoring, maven, etc.

Do you really think that it exists?

It does not.

To learn test automation with Selenium, you need to use a multitude of resources, books, blogs, youtube videos, online courses, webinars, etc.

Because the amount of information needed for an automation job is very high.

 

5. Learning by yourself

If you attempt learning by yourself, chances are high that you wont make it.

Find other people with the same goal, take instructor-led courses, find developers willing to help you, go to meetups, anything but being by yourself.

 

6. Selenium IDE

Record and play is not test automation

 

What is a selenium tester doing in real time

If you missed the previous day, you can find it here.

Morning of January 18

I start the day with something to bring my mood and energy up.
One cup of hot, strong coffee (no sugar, no milk) for the energy and Rammstein music for the mood.

Today, I need to focus on test stability issues.

I have around 20 Jenkins jobs that run daily automated tests related to various site features.

Some jobs pass consistently but a few fail from time to time.

The first one that I will look at is for the ChangePassword feature.

It runs all tests from the ChangePassword test class using a Maven command:

mvn clean -Dtest=automatedtests.changePassword.ChangePasswordTests test

Out of 5 job runs, there are 2 failures. The Jenkins console log says that assertions are failing. I will run the test class locally to see what is going on.

 

15 minutes later

From all change password tests, the one that changes the password successfully works. All other negative tests that verify that the password cannot be changed (if it is too short or if it includes special characters) fail. Each negative test attempts changing the password and then checks if an error message is displayed.

 

But the error message is all the time the same. It should say that the password is too short or that the password cannot include special characters. Instead, it always says that the passwords are not identical.

 

This seems like an application bug so I should speak to the developers. I will record a video with the tests execution and send it in an email asking for their opinion. Maybe there is a good explanation for it. I better get their opinion first and then, if they agree, log a bug to Jira.

 

10 minutes later

The email is out so I can look into the second failing Jenkins job. This one is about the user acount security questions.

The Jenkins console log points to timeout issues for different page elements.

There are a few possible reasons:

  • The site is very slow.
  • The test does not navigate from a page to the next one. Maybe the test executes the code for clicking a button and the button is not clicked.
  • The ids of the elements changed again.

The html of the site is pretty bad with many elements having either no ids or dynamic ids.

I will have to run these tests locally as well, then confirm which elements cannot be found, get their ids and compare them to the page element ids from Chrome Inspector.

So booooriiiing, does anyone like to do this part?

Anyways, as Maximus’s servant said it in Gladiator  (what a terrific movie! looking forward to watch it again) when asked if he likes what he does:

Maximus: Do you find it hard to do your duty?
Cicero: Sometimes I do what I want to do. The rest of the time, I do what I have to.

I will do as well what I have to.

Reluctantly 😦

 

Later in the day

I am really upset now. All elements from the security questions page have different ids. I need to modify all locators from the SecurityQuestions.java class and retest all tests that use it.

This is such a waste of effort, modifying page element locators over and over and over.

Starting from scratch 🙂

Too bad that the developers are so busy with the new features that no one has time to add the custom ids that I keep asking for.

I am so looking forward to the end of the sprint. Maybe after it, I will be able to pair with a developer and go through all pages and add custom ids to all elements.

Fingers crossed!

 

Finally, the last failing Jenkins job.

This one is for the user registration tests.

The console log shows stale element exceptions for a few elements. This is weird since I am sure that these tests were running very well.

Maybe the developers enabled the auto postback setting for the page elements which makes the element regenerate every time it is interacted with. I solved this before for other pages by retrying a method on exception a few times so I can do the same here. But since it is later in the day, it will have to wait until tomorrow.

 

4 pm already?

Before I go home, someone mentioned on LinkedIn that page factory should not be used any longer.

It is the video of the keynote of a Selenium conference from 2016. I better watch it to get more details.

 

4.30 pm

Yes, Simon Stewart says that no one is using Page Factory and that its design could have been better.

But apparently, no one uses it correctly either and many people try to generate page objects automatically while using page factory.

Wow, never crossed my mind to generate page objects automatically!

So Page Factory works if you use it as it is supposed to be used.

I should watch this video again.

I laughed so much during it, Simon has a great sense of humor 🙂

Use streams for lists of web elements

2179048[1]

Every time you need to find multiple elements in your Selenium code, you get a list of web elements, so a collection.

Streams and predicates, introduced in Java 8, are the best way of working with collections.

When you use them, you don’t only get code that is shorter and easier to understand but also better performance.

Why is this useful for you, a Selenium WebDriver developer?

It is useful because instead of getting texts and attributes of all elements (matched by the same locator) with

private List< String > textValues(By locator) {
  List elements = driver.findElements(locator);

  List< String > values = new ArrayList<>();

  for (WebElement e : elements)
    values.add(e.getText());

  return values;
}

private List< String > attrValues(By locator, String name) {
  List elements = driver.findElements(locator);

  List< String > values = new ArrayList<>();

  for (WebElement e : elements)
    values.add(e.getAttribute(name));

  return values;
}

you can use

private List< String > textValues(By loc) {
  return getValues(loc, e -> e.getText());
}

private List< String > attrValues(By loc, String name) {
  return getValues(loc, e -> e.getAttribute(name));

private List< String > getValues(
By loc, Function<WebElement,String > pred) {

  List< WebElement > elements = driver.findElements(loc);       

  List< String > values = elements.stream().map(pred)
                                  .collect(Collectors.toList());

  return values;


}

Read along to understand why the second code version is much better.

How to use collections the old way

Assume that you have a Result class for results of a page:

public class Result {

  private String name;
  private int price;
  private String owner;
  private boolean onlineOnly;

  public Result(String name, int price, String owner, boolean onlineOnly) {
    this.name = name;
    this.price = price;
    this.owner = owner;
    this.onlineOnly = onlineOnly;
  }

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

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

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

  public boolean onlineOnly() {
    return this.onlineOnly;
  }

  @Override
  public String toString() {
    return name + " - " + price + " - " + owner + " - " + onlineOnly;
  }

}

You need also a Results class (for lists of results):

public class Results {

  List< Result > results;

  public Results(List results) {
    this.results = results;
  }

  public List< Result > get() {
    return this.results;
  }

  public Result getResult(int i) {
    return this.results.get(i);
  }

  public int size() {
    return this.results.size();
  }

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

}

So far, Results class has methods for returning

  • the list of results
  • an element of the list
  • the size of the list
  • a String value for the list

Pretty basic, so far.

We would like to add filtering methods to the Results class such as:

public Results filterByOnlineOnly() {
  List< Result > list = new ArrayList()<>;

  for (Result r : this.results)
    if (r.onlineOnly() == true)
      list.add(r);

  return new Results(list);
}

public Results filterByOwner(String name) {
  List< Result > list = new ArrayList()<>;

  for (Result r : this.results)
    if (r.owner().equals(name))
      list.add(r);

  return new Results(list);
}

public Results filterByName(String name) {
  List< Result > list = new ArrayList()<>;

  for (Result r : this.results)
    if (r.name().equals(name))
      list.add(r);

  return new Results(list);
}

public Results filterByPrice(int price) {
  List< Result > list = new ArrayList()<>;

  for (Result r : this.results)
    if (r.price() == price)
      list.add(r);

  return new Results(list);
}

So far, we have filtering methods by name, owner, price and online only.

But, what should we do if we need more filtering methods such as

  • by price in a range
  • with a keyword in the name
  • with a keyword in the name and a specific owner

Adding more methods to the Results class is not the solution.

The more filters we need, the more methods.

One thing that is easy to notice is that all filtering methods have very similar code.

The only exception is the condition that the list elements should match.

How to use collections with streams and predicates

We can replace all methods with one method that uses a predicate parameter (for the filtering condition).

public Results filterBy(Predicate predicate) {

  List< Result > list = this.results.stream()
                  .filter(predicate)
                  .collect(Collectors.toList());

  return new Results(list);
}

How does this work?

  1. First, the results list is converted to a stream by the stream() method.
  2. Then, the filter() method filters the stream with the predicate . This is done by going through each element of the stream, applying the predicate to each element and selecting only the elements that match the predicate.
  3. Finally, collect() collects all selected elements and returns them as another list.

Amazing, isn’t it?

How can this method be used?

Results products = results.filterBy(r -> r.onlineOnly() == true);

Results products = results.filterBy(r -> r.name().equals("IPHONE 8"));

Results products = results.filterBy(r -> r.price() == 500);

We can get really creative now:

Results list = results.filterBy(r -> r.name().contains("IPHONE"));

Results products = results.filterBy(r -> r.name().contains("IPHONE") &&
                                         r.price() > 500 &&
                                         r.price() < 1000 &&
                                         r.onlineOnly() == false);

What else can you do with streams?

How about creating the list of all names, or all prices or all owners?

public List < String > values(Predicate predicate) {
  List list = this.results.stream()
                          .map(predicate)
                          .collect(Collectors.toList());

  return list;
}

Notice that the filter() method is replaced by the map().
map() extracts the value as specified by the predicate.
The method returns a list of Strings since names and owners are Strings:

List< String > owners = results.values(r -> r.owner());

List< String > names = results.values(r -> r.names());

There are many other things that can be done with streams and predicates:

Results onlineProducts = results.stream()
                                .filter(Result::onlineOnly)
                                .skip(1)
                                .collect(Collectors.toList());

Results cheapProducts = results.stream()
                           .filter(r -> r.price() <= 100)                                               .filter(r -> r.owner().equals("Rogers"))
                           .collect(Collectors.toList());

List< String > distinctOwners = results.stream()
                             .map(Result::owner)
                             .distinct()
                             .collect(Collectors.toList());

int priceSum = results.stream()
                      .map(Result::price)
                      .reduce(0, Integer::sum);

assertTrue(results.stream()
                  .anyMatch(r -> r.owner().equals("Bell")));

Java 8 In Action is a great book for learning more on streams and predicates.

You dont need static driver or static methods

You dont need a static driver or static methods in Selenium test automation.

Because they bring more problems than benefits.

Such as breaking encapsulation, making objects mutable and promoting procedural code.

not-sure-why-you-dont-like-static

Enough technical terms, lets talk common sense.

We have a test for a successful user log in:

public class LoginTests {

 private WebDriver driver;
 private final String EMAIL = "test@test.com";
 private final String PASSWORD = "password123";
  
 @Before
 public void setUp() {
   driver = new FirefoxDriver();
 }
  
 @After
 public void tearDown() {
   driver.quit();
 }
  
 @Test
 public void successfulLoginWorks() {
   LoginPage loginPage = new LoginPage(driver);
   loginPage.open();
    
   MainPage mainPage = loginPage.loginAs(EMAIL, PASSWORD);
   assertTrue(mainPage.isUserNameDisplayed() == true);
 }
  
}

 

It uses 2 page objects:

public class LoginPage {

 private final String URL = "http://www.abc.com";
 private final String TITLE = "Login Page";
 private final By EMAIL_ID = By.id("emailId");
 private final By PASSWORD_ID = By.id("passwordId");
 private final By LOGIN_ID = By.id("loginBtnId");

 private WebDriver driver;
 private WebDriverWait wait;

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

 public void open() {
   driver.get(URL);
   if (title().equals(TITLE) == false)
     throw new RuntimeException("Home Page is not displayed!");
 }
  
 public String url() {
   return this.driver.getCurrentUrl();
 }
  
 public String title() {
   return this.driver.getTitle();
 }

 public MainPage loginAs(String username, String password) {
   WebElement emailTextBox = 
   wait.until(visibilityOfElementLocatedBy(EMAIL_ID));
   emailTextBox.sendKeys(username);
    
   WebElement passwordTextBox = 
   wait.until(visibilityOfElementLocatedBy(PASSWORD_ID);
   passwordTextBox.sendKeys(password);
    
   WebElement loginButton = 
   wait.until(visibilityOfElementLocatedBy(LOGIN_ID);
   loginButton.click();
    
   return new MainPage(this.driver);
 }
}

 

And MainPage …..

public class MainPage {
 private final String URL = "http://www.abc.com/main";
 private final By USERNAME_ID = By.id("userNameId");
  
 private WebDriver driver;
 private WebDriverWait wait;

 public MainPage(WebDriver d) {
   this.driver = d;
   this.wait = new WebDriverWait(this.driver, 30);
   if (url().equals(URL) == false)
     throw new RuntimeException("Main Page is not displayed!");
 }

 public String url() {
   return this.driver.getCurrentUrl();
 }
  
 public boolean isUserNameDisplayed() {
   WebElement userNameLabel = 
   wait.until(visibilityOfElementLocatedBy(USERNAME_ID));
   
   return userNameLabel.isDisplayed();
 }

}

 

The test can be improved in 2 places.

First, the driver is passed to the LoginPage object.

Second, we create a LoginPage object in the test.

 

How is the driver used by the page classes?

The test creates a LoginPage object and passes the driver object to it:

LoginPage loginPage = new LoginPage(driver);

 

The LoginPage constructor gets the driver as parameter and saves it in a class field:

public LoginPage(WebDriver d) {
 this.driver = d;

 

LoginPage class passes the driver to a MainPage object in the loginAs() method:

public MainPage loginAs(String username, String password) {
 .......    
 return new MainPage(this.driver);
}

 

The MainPage constructor gets the driver as parameter and saves it in a class field.

The driver object is passed around by the page classes.

 

How can we fix these 2 problems?

One way would be with a static driver and static page methods.

 

exactly-my-point-static-is-the-only-way

 

First, we create a BaseTest class as a parent for test classes.

We move to it the driver object and make it static.

We also move to it the setUp() and tearDown() methods, make them static and change their annotations from @Before and @After to @BeforeClass and @AfterClass:

public class BaseTest {

 public static WebDriver driver;

 @BeforeClass
 public static void setUp() {
   driver = new FirefoxDriver();
 }
  
 @AfterClass
 public static void tearDown() {
   driver.quit();
 }

}

 

Next, we make the LoginTest class inherit from BaseTest and remove the driver object:

public class LoginTests extends BaseTest {
 private final String EMAIL = "test@test.com";
 private final String PASSWORD = "password123";

 @Test
 public void successfulLoginWorks() {
   LoginPage loginPage = new LoginPage();
   loginPage.open();
    
   MainPage mainPage = loginPage.loginAs(EMAIL, PASSWORD);
   assertTrue(mainPage.isUserNameDisplayed() == true);
 }
  
}

 

Notice that there is no driver, setUp() and tearDown() methods in LoginTests.java.

How do the page classes access the static driver?

 

A static import for the BaseTest class is added both to LoginPage and MainPage classes:

import static BaseTest.*;

 

Because of it, since the driver field of BaseTest is public, the page classes can use it as if it is local to the page class.

import static BaseTest.*;

public class LoginPage {

 private final String URL = "http://www.abc.com";
 private final String TITLE = "Login Page";
 private final By EMAIL_ID = By.id("emailId");
 private final By PASSWORD_ID = By.id("passwordId");
 private final By LOGIN_ID = By.id("loginBtnId");

 private WebDriverWait wait = new WebDriverWait(driver, 30);

 public void open() {
   driver.get(URL);
   if (title().equals(TITLE) == false)
     throw new RuntimeException("Home Page is not displayed!");
 }
  
 public String url() {
   return driver.getCurrentUrl();
 }
  
 public String title() {
   return driver.getTitle();
 }

 public MainPage loginAs(String username, String password) {
   WebElement emailTextBox = 
   wait.until(visibilityOfElementLocatedBy(EMAIL_ID));
   emailTextBox.sendKeys(username);
    
   WebElement passwordTextBox = 
   wait.until(visibilityOfElementLocatedBy(PASSWORD_ID);
   passwordTextBox.sendKeys(password);
    
   WebElement loginButton = 
   wait.until(visibilityOfElementLocatedBy(LOGIN_ID);
   loginButton.click();
    
  return new MainPage();
 }
}

 

And MainPage …..

import static BaseTest.*;

public class MainPage {
 private final String URL = "http://www.abc.com/main";
 private final By USERNAME_ID = By.id("userNameId");
  
 public MainPage() {  
   if (url().equals(URL) == false)
     throw new RuntimeException("Main Page is not displayed!");
 }

 public String url() {
   return driver.getCurrentUrl();
 }
  
 public boolean isUserNameDisplayed() {
   WebElement userNameLabel = 
   wait.until(visibilityOfElementLocatedBy(USERNAME_ID));
   return userNameLabel.isDisplayed();
 }

}

 

Notice that there are no driver parameters for the constructors of the page classes.

The driver fields are removed as well.

Every time a page method needs the driver object, it does not prefix it with this any longer since the driver is not a class field.

 

Great, there is no driver in the test!

 

How do we remove the LoginPage object?

We can make all page methods static for both classes.

The public page methods are all declared as static and do not return any objects.

All page fields become static as well.

import static BaseTest.*;

public class LoginPage {
 private static final String URL = "http://www.abc.com";
 private static final String TITLE = "Login Page";
 private static final By EMAIL_ID = By.id("emailId");
 private static final By PASSWORD_ID = By.id("passwordId");
 private static final By LOGIN_ID = By.id("loginBtnId");

 private static WebDriverWait wait = new WebDriverWait(driver, 30);

 public static void open() {
   driver.get(URL);
   if (title().equals(TITLE) == false)
     throw new RuntimeException("Home Page is not displayed!");
 }
  
 public static String url() {
   return driver.getCurrentUrl();
 }
  
 public static String title() {
   return driver.getTitle();
 }

 public static void loginAs(String username, String password) {
   WebElement emailTextBox = 
   wait.until(visibilityOfElementLocatedBy(EMAIL_ID));
   emailTextBox.sendKeys(username);
    
   WebElement passwordTextBox = 
   wait.until(visibilityOfElementLocatedBy(PASSWORD_ID);
   passwordTextBox.sendKeys(password);
    
   WebElement loginButton = 
   wait.until(visibilityOfElementLocatedBy(LOGIN_ID);
   loginButton.click();	
 }
}

 

And MainPage class …

import static BaseTest.*;

public class MainPage {
 private static final String URL = "http://www.abc.com/main";
 private static final By USERNAME_ID = By.id("userNameId");
  
 private static WebDriverWait wait = new WebDriverWait(driver, 30);
  
 public static void open() {
   if (url().equals(URL) == false)
     throw new RuntimeException("Main Page is not displayed!");
 }
  
 public static String url() {
   return driver.getCurrentUrl();
 }
  
 public static boolean isUserNameDisplayed() {
   WebElement userNameLabel = 
   wait.until(visibilityOfElementLocatedBy(USERNAME_ID));
  
   return userNameLabel.isDisplayed();
 }

}

 

Since all methods are static and we dont want any objects in the test, the page methods do not return any objects.

We do not have constructors either.

For the MainPage class, this creates a problem since we were validating in the constructor that the page is displayed.

The loginAs() method does not return a MainPage object too.

To go around all these restrictions, we create an open() method in the MainPage class that does not open the page but validates that it is displayed.

 

How does the test look with static methods?

public class LoginTests extends BaseTest {

 @Test
 public void successfulLoginWorks() {
   LoginPage.open();
   LoginPage.loginAs(EMAIL, PASSWORD);
   MainPage.open();
   assertTrue(MainPage.isUserNameDisplayed() == true);
 }
  
}

 

Very convenient, is it not?

No driver, no LoginPage object, no setUp() and tearDown()!!!

All these are achieved with static methods, static variables and a static driver.

 

yep-you-are-wasting-my-time

 

Is this correct?

I dont think it is.

Lets look first at concerns about the public static driver.

Our driver is a public field of the BaseTest class.

Class fields should always be private so that they cannot be accessed and modified from outside of the class.

It should not be possible to change the fields of a class since this means changing the state of the class’s objects.

This is called immutability in programming meaning that the state of an object should not be changed after the object is created.

Our driver is also static.

Java code should be object oriented so it should use objects.

Each object encapsulates fields and methods and its methods should work on the class fields and the methods parameters only.

Using external static variables in a class means that we break the encapsulation concept.

The state of the object is known when the object’s methods only use the object’s fields.

When external static variables are used as well, the state of the object is not known any longer.

Finally, the static page methods.

Because of using static page methods, all class fields must be static as well.

So the page class uses everything static.

We dont need objects any longer when using static methods.

This is wrong for the same reasons already discussed (immutability, encapsulation).

 

But there is one more reason for not using static variables and methods.
Static methods and variables encourage procedural code to be written instead of object oriented.

 

Now, it seems like we are failing at automation.

It is not too bad to just have a static driver variable.

But using static in other ways is not recommended.

 

static-is-great-you-dont-know-how-to-use-it

So, if static is bad, how do we solve our problems?

First, how important are these problems?

Having the driver passed around by the page classes is not a big deal.

It is a minor case of duplication that is ok to live with.

For the driver in the test and the page object, we can solve this in a different way.

First, we go back to the previous code version where nothing static was used.

Then, we change the BaseTest class by adding a method to open the site:

public class BaseTest {

 private final String URL = "http://www.abc.com";
  
 public WebDriver driver;

 @Before
 public void setUp() {
   driver = new FirefoxDriver();
 }
  
 @After
 public void tearDown() {
   driver.quit();
 }

 public LoginPage openSite() {
   driver.get(URL);
	 
   return new LoginPage(this.driver);
 }
  
}

 

Notice that BaseTest uses non-static methods again and @Before/@After annotations.

The test becomes:

public class LoginTests {

 @Test
 public void successfulLoginWorks() {
   LoginPage loginPage = openSite();
   loginPage.open();
    
   MainPage mainPage = loginPage.loginAs(EMAIL, PASSWORD);
   assertTrue(mainPage.isUserNameDisplayed() == true);
 }
  
}

 

Now, openSite() launches the site and returns the LoginPage object.

The LoginPage object encapsulates the driver and passes it to the MainPage object.

 

i-am-not-convinced-try-again

 


 

If you liked this article,

you may be interested in my new KINDLE book about how to simplify Selenium code with automation patterns:

Improve Selenium Code with Automation Patterns:

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

 

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.

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