Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to implement user types for @FindBy annotation?

I'm trying to get from this:

@FindBy(xpath = "//div/span/img")
public WebElement addNew;

@FindBy(xpath = "//tr[2]/td[12]")
public WebElement save;

@FindBy(xpath = "//td/div/input")
public WebElement entryIdel;

@FindBy(xpath = "//textarea")
public WebElement authorFieldel;

@FindBy(xpath = "//td[3]/div/textarea")
public WebElement titleFieldel;

that:

@FindBy(xpath = "//div/span/img")
public Button addNew;

@FindBy(xpath = "//tr[2]/td[12]")
public Button save;

@FindBy(xpath = "//td/div/input")
public InputBox entryIdel;

@FindBy(xpath = "//textarea")
public InputBox authorFieldel;

@FindBy(xpath = "//td[3]/div/textarea")
public InputBox titleFieldel;

I have previously created class for each element, but of course nothing happens. How i can create my element class so that i can use it instead of WebElement?

Here the code of InputBox at this moment:

 import org.openqa.selenium.WebElement;

  public class InputBox {

protected WebElement element;

public WebElement getElement() {
    return element;
}

public InputBox(WebElement element) {
    this.element = element;
    // TODO Auto-generated constructor stub
}

public void type(String input) {
    clearText();
    element.sendKeys(input);
}

public void clearText() {
    element.clear();
}

public boolean isEditable() {
    return element.isEnabled();
}

String getText() {
    return element.getText();
}

String getValue() {
    return element.getValue();
}

}
like image 335
Arthur Avatar asked Feb 28 '12 08:02

Arthur


People also ask

What is the use of @FindBy annotation?

Annotation Type FindBy Used to mark a field on a Page Object to indicate an alternative mechanism for locating the element or a list of elements. Used in conjunction with PageFactory this allows users to quickly and easily create PageObjects. It can be used on a types as well, but will not be processed by default.

How do I get a list of elements using FindBy?

@FindBy(id="element_id") public List<WebElement> selects; You now have a list of all the web elements with that ID. Then, you just grab the element out of the list like you would any other PageFactory WebElement list.

What is @FindBy annotation in Selenium?

@FindBy: An annotation used in Page Factory to locate and declare web elements using different locators.

Which are the locators supported by the annotation FindBy ()?

@FindBy can accept tagName, partialLinkText, name, linkText, id, css, className, xpath as attributes.


3 Answers

Create a new implementation of FieldDecorator.

When you use the PageFactory you are probably calling

 public static void initElements(ElementLocatorFactory factory, Object page)

This would become

 public static void initElements(FieldDecorator decorator, Object page)

Your FieldDecorator could behave similarly to the DefaultFieldDecorator except wrap the proxy in your custom type.

See the classes here [source]

like image 95
roby Avatar answered Oct 11 '22 12:10

roby


I have found a very interesting post about how @FindBy works and how to use FieldDecorator in Selenium (WebDriver) based tests: http://habrahabr.ru/post/134462/.

The author of the post is Роман Оразмагомедов (Roman Orazmagomedof).

Here I am giving more explanation on how to use FieldDecorator. Also I will be showing the extended version of the original implementation with additional functionality which will allow waiting for a decorated field to be ready by using ExpectedCondition interface.

Setting Objectives

Most illustrations of Selenium page object pattern are using WebElement interface to define the pages’ fields:

public class APageObject {    

    @FindBy(id="fieldOne_id")  

    WebElement fieldOne;


    @FindBy(xpath="fieldTwo_xpath")

    WebElement fieldTwo;


    <RESTO OF THE Page IMPLEMENTATION>

}

I would like:

a) A page to be a more generic container with the ability to combine several forms together.

b) To use plain java objects instead of WebElement interface to declare fields on a page.

c) To have a simple way to determine if an element on a page is ready to be used or not.

For example:

public class PageObject  {

        private APageForm formA;

        <OTHER FORMS DECLARATIONS >

        public void init(final WebDriver driver) {

            this.driver = driver;

            formA = new APageForm());

            PageFactory.initElements(new SomeDecorator(driver), formA);

                <OTHER FORMS INITIALIZATION>

        }

        <THE REST OF  the PAGE IMPLEMENTATION>

}

Where APageForm looks similar to a APageObject, but with a little difference – each field in the form is defined by a dedicated java class.

public class APageForm {

    @FindBy(id="fieldOne_id")  

    FieldOne fieldOne;



    @FindBy(xpath="fieldTwo_xpath")

    FieldTwo fieldTwo;

    <REST OF THE FORM IMPLEMENTATION>

}

There are two more important points to remember:

a) This approach should use Selenium ExpectedCondition;

b) This approach should help to separate code between “data delivery” and “data assertion”.

  1. Element

    public interface Element {

       public boolean isVisible();
    
       public void click();
    
       public ExpectedCondition<WebElement> isReady();
    

    }

This interface should be extended for more complex elements like button, link, label etc. For example:

public interface TextField extends Element {

       public TextField clear();

       public TextField enterText(String text);

       public ExpectedCondition<WebElement> isReady();

}

Each element should provide isReady() to avoid usage of the Thread.sleep().

Every implementation of an element should extend AbstractElement class:

public abstract class AbstractElement implements Element {

       protected WebElement wrappedElement;



protected AbstractElement (final WebElement el) {

              this.wrappedElement = el;

       }

       @Override

       public boolean isVisible() {

              return wrappedElement.isDisplayed();

       }     

       @Override

       public void click() {

           wrappedElement.click();    

       }     

       public abstract ExpectedCondition<WebElement> isReady();     

}

For example:

public class ApplicationTextField extends AbstractElement implements TextField {

       public ApplicationTextField(final WebElement el) {

              super(el);

       }

       @Override

       public TextField clear() {

              wrappedElement.clear();

              return this;

       }

       @Override

       public TextField enterText(String text) {

              char[] letters = text.toCharArray();

              for (char c: letters) {                 

                     wrappedElement.sendKeys(Character.toString(c));

                     // because it is typing too fast...

                     try {

                           Thread.sleep(70);

                     } catch (InterruptedException e) {

                           e.printStackTrace();

                     }

              }

              return this;

       }

       @Override

       public ExpectedCondition<WebElement> isReady() {

              return ExpectedConditions.elementToBeClickable(wrappedElement);

       }

}

The following interface describes an element factory:

public interface ElementFactory {

       public <E extends Element> E create(Class<E> containerClass, WebElement wrappedElement);

}

the implementation of the element factory is:

public class DefaultElementFactory implements ElementFactory {

       @Override

       public <E extends Element> E create(final Class<E> elementClass,

                     final WebElement wrappedElement) {

              E element;

              try {

                     element = findImplementingClass(elementClass)

                                  .getDeclaredConstructor(WebElement.class)

                                  .newInstance(wrappedElement);

              }

              catch (InstantiationException e) { throw new RuntimeException(e);}

              catch (IllegalAccessException e) { throw new RuntimeException(e);}

              catch (IllegalArgumentException e) {throw new RuntimeException(e);}

              catch (InvocationTargetException e) {throw new RuntimeException(e);}

              catch (NoSuchMethodException e) { throw new RuntimeException(e);}

              catch (SecurityException e) {throw new RuntimeException(e);}

              return element;

       }



       private <E extends Element> Class<? extends E> findImplementingClass (final Class<E> elementClass) {

              String pack = elementClass.getPackage().getName();

              String className = elementClass.getSimpleName();

              String interfaceClassName = pack+"."+className;

              Properties impls = TestingProperties.getTestingProperties().getImplementations();

              if (impls == null) throw new RuntimeException("Implementations are not loaded");

              String implClassName = impls.getProperty(interfaceClassName);

              if (implClassName == null) throw new RuntimeException("No implementation found for interface "+interfaceClassName);

              try {

                     return (Class<? extends E>) Class.forName(implClassName);

              } catch (ClassNotFoundException e) {

                     throw new RuntimeException("Unable to load class for "+implClassName,e);

              }

       }

}

The factory reads a property file to use a desired implementation for an element:

com.qamation.web.elements.Button = tests.application.elements.ApplicationButton

com.qamation.web.elements.Link = tests.application.elements.ApplicationLink

com.qamation.web.elements.TextField = tests.application.elements.ApplicationTextField

com.qamation.web.elements.Label=tests.application.elements.ApplicationLabel

The element factory is going to be used by an implementation of the FieldDecorator interface. I will discuss this below.

At this point the elements' part coverage is completed. Here is the summary:

Each element is described by an interface which is extending Element interface.

Every element’s implementation extends the AbstractElement class and completes isReady(), along with other required methods.

Desired element’s implementation should be defined in a property file.

The element factory will instantiate an element and pass it to the PageFactory.initElement() via the decorator.

It seems complicated at first.

It becomes very convenient to create and use simple elements to model complex forms and pages.

  1. Container.

A container is a facility to keep elements and other containers together in order to model complex web forms and pages.

The container structure is similar to the element however it's simpler.

A container is defined by an interface:

public interface Container  {

       public void init(WebElement wrappedElement);

       public ExpectedCondition<Boolean> isReady(WebDriverWait wait);

}

The container has its AbstractContainer base class:

public abstract class AbstractContainer implements Container{

       private WebElement wrappedElement;

       @Override

       public void init(WebElement wrappedElement) {

              this.wrappedElement = wrappedElement;

       }     

       public abstract ExpectedCondition<Boolean> isReady(final WebDriverWait wait);

}

It is important to pay attention to the container’s init method: method’s parameter is an instance of the WebElement interface.

Similar to an element, a container should implement the isReady() method. The difference is in the return type: ExpectedCondition.

The “Ready” condition of a container depends on the combination of the elements included into the container.

It is logical to combine several conditions into one using Boolean type.

Here is an example of a container:

public class LoginContainer extends AbstractContainer{

       @FindBy(id="Email")

       private TextField username;

       @FindBy(id="Passwd" )

       private TextField password;

       @FindBy(id="signIn")

       private Button submitButton;

       public void login(final String username, final String password) {

              this.username.clear().enterText(username);

              this.password.clear().enterText(password);

              this.submitButton.press();

       }

       @Override

       public ExpectedCondition<Boolean> isReady(final WebDriverWait wait) {     

              return new ExpectedCondition<Boolean>() {

                     @Override

                     public Boolean apply(final WebDriver driver) {

                           ExpectedCondition isUserNameFieldReady = username.isReady();

                            ExpectedCondition isPasswordFieldReady = password.isReady();

                            ExpectedCondition isSubmitButtonReady = submitButton.isReady();

                           try {

                                  wait.until(isUserNameFieldReady);

                                  wait.until(isPasswordFieldReady);

                                  wait.until(isSubmitButtonReady);

                                  return new Boolean(true);

                           }

                           catch (TimeoutException ex) {

                                  return new Boolean(false);

                            }                         

                     }

              };

       }

}

Container factory defined by an interface:

public interface ContainerFactory {

       public <C extends Container> C create(Class<C> wrappingClass, WebElement wrappedElement);

}

container factory's implementation is much simpler than element’s factory:

public class DefaultContainerFactory implements ContainerFactory {

       @Override

       public <C extends Container> C create(final Class<C> wrappingClass,

                     final WebElement wrappedElement) {

              C container;

              try {

                     container = wrappingClass.newInstance();

              }

catch (InstantiationException e){throw new RuntimeException(e);}

catch (IllegalAccessException e){throw new RuntimeException(e);}

              container.init(wrappedElement);

              return container;

       }

}

Here is a short summary for the container:

A container is used to combine elements and other containers into one unit.

A container's implementation should extend from the AbstructContainer class. It should implement isReady() and other methods required by the container.

A container will be instantiated and passed to the PageFactory.initElement() by the container factory through a decorator.

  1. Page

A page is a bridge between a WebDriver instance and containers. A Page helps to decouple WebDriver from testing activities, test data provisioning and test results verification.

A Page is defined by an interface, similar to the Container:

public interface Page {

       public void init(WebDriver driver);

}

The difference between a container and a page is in the init():

public abstract class AbstractPage implements Page {

       protected WebDriver driver;

       @Override

       public void init(WebDriver driver) {

              this.driver = driver;

       }

}

Page’s init method takes a WebDriver instance as a parameter.

A page implementation should extend the AbstractPage class. For example, a simple gmail page:

public interface GMailPage extends Page {

       public NewEmail startNewEmail();

}

public class DefaultGMailPage extends AbstractPage implements GMailPage {

       private LeftMenueContainer leftMenue;

       public void init(final WebDriver driver) {

              this.driver = driver;

              leftMenue = new LeftMenueContainer();          

              PageFactory.initElements(new DefaultWebDecorator(driver), leftMenue);

              WebDriverWait wait = new WebDriverWait(driver,TestingProperties.getTestingProperties().getTimeOutGeneral());

        ExpectedCondition<Boolean> isEmailFormReady = leftMenue.isReady(wait);

        wait.until(isEmailFormReady);

       }

       @Override

       public NewEmail startNewEmail() {       

              leftMenue.pressCompose();

              NewEmailWindowContainer newEmail = new NewEmailWindowContainer();

        PageFactory.initElements(new DefaultWebDecorator(driver), newEmail);

        WebDriverWait wait = new WebDriverWait(driver,TestingProperties.getTestingProperties().getTimeOutGeneral());

        ExpectedCondition<Boolean> isNewEmailReady=newEmail.isReady(wait);

              wait.until(isNewEmailReady);

              return newEmail;

       }

}

Components summary:

Element -> AbstractElement -> Element’s Emplementations -> Element Factory

Container -> AbstractContainer -> Container Factory

Page -> AbstractPage.

  1. Decorator

The constructions described above become alive when PageFactory.initElements() calls the provided decorator.

A basic implementations is already exists – DefaultFieldDecorator. Lets use it.

public class DefaultWebDecorator extends DefaultFieldDecorator {

       private ElementFactory elementFactory = new DefaultElementFactory();

       private ContainerFactory containerFactory = new DefaultContainerFactory();



       public DefaultWebDecorator(SearchContext context) {

              super(new DefaultElementLocatorFactory(context));

       }

       @Override

       public Object decorate(ClassLoader classLoader, Field field) {

              ElementLocator locator = factory.createLocator(field);

              WebElement wrappedElement = proxyForLocator(classLoader, locator);

              if (Container.class.isAssignableFrom(field.getType())) {

                     return decorateContainer(field, wrappedElement);

              }

              if (Element.class.isAssignableFrom(field.getType())) {

                     return decorateElement(field, wrappedElement);

              }

              return super.decorate(classLoader, field);

       }

       private Object decorateContainer(final Field field, final WebElement wrappedElement) {

              Container container = containerFactory.create((Class<? extends Container>)field.getType(), wrappedElement);

              PageFactory.initElements(new DefaultWebDecorator(wrappedElement), container);           

              return container;

       }



       private Object decorateElement(final Field field, final WebElement wrappedElement) {

              Element element = elementFactory.create((Class<? extends Element>)field.getType(), wrappedElement);

              return element;

       }

}

Note that the decorateContainer() does not exit until all sub elements and containers are not initialized.

Now, let’s look at a simple test that presses Compose button on the gmail page and checks if a new email window appears on the screen:

public class NewEmailTest {

       private WebDriver driver; 

       @BeforeTest

       public void setUp() {

              driver = new FirefoxDriver();

              driver.manage().window().maximize();

       }     

       @AfterTest

       public void tearDown() {

              driver.close();

       }     

       @Test (dataProvider = "inputAndOutput", dataProviderClass = com.qamation.data.provider.TestDataProvider.class)

       public void startNewEmailTest(DataBlock data) {

              DefaultHomePage homePage = new DefaultHomePage();

              driver.manage().deleteAllCookies();     

              driver.get(data.getInput()[0]);

              homePage.init(driver);

              NewEmail newEmail = homePage.signIn().login(data.getInput()[1], data.getInput()[2]).startNewEmail();           



              for (String[] sa : data.getExpectedResults()) {

                  WebElement el = driver.findElement(By.xpath(sa[0]));

                  Assert.assertTrue(el.isDisplayed());

              }

       }

}

When running the test from Eclipse, the following VM arguments need to be used:

-DpropertiesFile=testing.properties

The source and several more articles about QA and QA Automation can be found here http://qamation.blogspot.com

like image 28
GPawel Avatar answered Oct 11 '22 12:10

GPawel


First guess: Have you been thinking about better naming convenion. In my class, the buttons look like this:

private WebElement loginButton;

In my selenium tests I found out, that better approach is to have Class for each page, like:

public Class LoginPage{
  private WebElement loginButton;
  private WebElement loginField;
  private WebElement passwordField;
  private WebDriver driver;

  public LoginPage(WebDriver drv){
  this.driver = drv;
  }

  public void login(String uname; String pwd){
   loginButton = driver.findElement(By.xpath("//td/div/input"));
   passwordField = driver...
   loginField = driver...
   loginField.sendKeys(uname);
   passwordField.sendkeys(pwd);      
   loginButton.click();
  }

  }

And then the test looks like this:

public void testLogin(){
 WebDriver driver = new FirefoxDriver();
 driver.get("http://the-test-page.com/login.htm");
 LoginPage loginPage = new LoginPage(driver);
 loginPage.login("username", "password");
}

but assuming that this does not work for you, there is my two guseeses:

First, you can extend from WebElement:

public class Button extends WebElement{

but you will probably have to implement all the WebElement public methods even if you are not using them

Then as Second guess you can send driver and find path to the constructor

public class Button {
 private WebDriver driver;
 private WebElement button;
 private WebDriver driver;

 public Button(WebDriver driver, By by){
    this,driver = driver;
    button = findElement(by);
 }

and the calling would be:

Button loginButton = new Button(driver, By.xpath("//td/div/input"));

BTW my assumtion here is, that you are using the WebDriver approach

EDIT I found out, that WebElement is interface. So you can do something like this:

public class WebButton implements WebElement{

but you will have to implement all abstract methods of interface WebElement.

Anyways, when I did it, it allowed me to do this annotation in my other class:

@FindBy(xpath = "//textarea")
public WebButton testButton;

but I never used that approach and cannot guarantee that it will do something...

BTW if interested, I pasted the example of my implementation here:

http://pastebin.com/STr15UQd

like image 1
Pavel Janicek Avatar answered Oct 11 '22 11:10

Pavel Janicek