Reflections and automatic way of obligatory fields validation
If you have your own Selenium WebDriver framework with page objects and think that you really need easy way to mark with annotations fields that should be validated when page is displayed - I mean fields that have to be always visible on that very page. That kind of validation can be done on nice generic way with use of java and reflections. If you need such validation every time the page is displayed, then familiarize with described solution.
To achieve the goal we need to create a few small elements. Create the annotation which describe the purpose of annotating in the best way. I have called the class Obligatory because in my case all fields marked with this annotation should be displayed on the page and does not matter from which state I have moved to the page (there were many states in the application). Below I shown example of the annotation class:
package com.testcraftsmanship.model.annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface Obligatory { }
Now lets create the class which will do the whole magic. I have shown below the easiest
but maybe not the prettiest solution. Lets assume that we have abstract Page object from
which all our pages extends. Let's look at the code below and discuss what is there:
package com.testcraftsmanship.page; import com.testcraftsmanship.model.annotation.Obligatory; import com.testcraftsmanship.model.support.DriverFactory; import org.openqa.selenium.By; import org.openqa.selenium.WebElement; import org.openqa.selenium.support.FindBy; import org.openqa.selenium.support.pagefactory.Annotations; import java.lang.reflect.Field; import static com.testcraftsmanship.model.support.ElementWait.await; public abstract class Page { protected Page() { checkObligatoryFields(); } public void checkObligatoryFields() { Class<?> validatingComponent = getClass(); while (validatingComponent != Object.class) { Field[] fields = validatingComponent.getDeclaredFields(); for (Field field : fields) { boolean obligatory = field.isAnnotationPresent(Obligatory.class); boolean hasFindBy = field.isAnnotationPresent(FindBy.class); boolean isElement = WebElement.class.isAssignableFrom(field.getType()); if (hasFindBy && isElement && obligatory) { By obligatoryByLocator = new Annotations(field).buildBy(); await().untilVisible(findElement(obligatoryByLocator)); } } validatingComponent = validatingComponent.getSuperclass(); } } public WebElement findElement(By by) { return DriverFactory.getDriver().findElement(by); } }
The most important part is the method checkObligatoryFields(). I this method we are getting
the class name for which we want to validate the fields (any class which extends Page class) and
analyse all fields for the class and all parents classes. For every field it is checked if it has
@Obligatory annotation, has FindBy annotation (we have to know what kind of element have to be
visible on the page) and if it is WebElement type field. If all those checks are passed then
we assume that the field has to be verified if it is really visible on the page. To do that
we are creating the By locator based on data from @FindBy annotation and wait for visibility of
that element (can be used method from ExpectedConditions class).
The next important part is to add execution of this method to the constructor of the class. Then we are sure that all fields marked with @Obligatory annotation are validated whenever the page object is created.
Lets looks at the example page object with use of this annotation:
package com.testcraftsmanship.page; import com.testcraftsmanship.model.annotation.Obligatory; import org.openqa.selenium.WebElement; import org.openqa.selenium.support.FindBy; import static com.testcraftsmanship.model.support.ElementWait.await; public class GoogleMainPage extends Page { @Obligatory @FindBy(css = "input[name='btnK']") private WebElement searchButton; @FindBy(css = "input[name='btnI']") private WebElement luckyButton; public GoogleMainPage clickLuckyButton() { await().untilVisible(luckyButton) .click(); return this; } public GoogleMainPage clickSearchButton() { searchButton.click(); return this; } }
The important thing is to remember that we can't mark with Obligatory annotation fields
which can change the state from visible to hidden or from active to inactive - it can
cause the problems and misunderstandings. If we want to use it we have to be sure how
our application works and if it really make sense to use this approach.
It is really useful if we have application in which we have many paths and from all those paths only some elements should be always visible. By setting annotations in one class we can select which fields will be validated in all paths.