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?

 

One Comment

Leave a comment