abhijit.bhatta
abhijit.bhatta

Reputation: 73

Fetch Pagefactory WebElement name using reflection

I am using selenium pagefactory, and have introduced an annotation @Name(Description = "Username"), which I use for all WebElements. I need to find the value for Description in my custom methods later on, for reporting, like:

public static void click(WebElement element) throws Throwable {
        try {
            element.click();
        } catch(ElementNotInteractableException E1) {
            throw new UnableToInteractWithElementException("Unable To Interact With " + WebElement Descriptionn);
        }
    }

My @Name annotation interface and pagefactory look like this:

Name Interface

@Target({ElementType.FIELD, ElementType.TYPE,ElementType.CONSTRUCTOR})
public @interface Name {    
    String Description() default "";
}
@Name(Description = "Username")
@FindBy(id="txtLoginID")
public WebElement username;

The problem I face while using reflections is the need to define classname of pagefactory , and also provide fieldname as string "username" , to retrieve the annotation value.

I wish to be able to retrieve the annotation value by only providing my WebElement and no other object to my click(WebElement element) method.

Upvotes: 7

Views: 1306

Answers (2)

Fenio
Fenio

Reputation: 3625

I can show you how to get the description partially using reflection.

First of all, we have to fix the Annotation @Name because you didn't add RetentionPolicy:

@Target({ElementType.FIELD, ElementType.TYPE, ElementType.CONSTRUCTOR})
@Retention(value = RetentionPolicy.RUNTIME)
public @interface Name {
    String Description() default "";
}

Now, what I did and tested, we create storage for the names and we're gonna store them when initializing the Page Objects like this:

public class NameStore {
    public static HashMap<WebElement, String> map = new HashMap<>();

    public static void store(Object pageObject) {
        Field[] fields = pageObject.getClass().getDeclaredFields();
        for (Field field : fields) {
            Name annotation = field.getAnnotation(Name.class);
            if (annotation != null) {
                try {
                    map.put((WebElement) field.get(pageObject), annotation.Description());
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public static String getDescription(WebElement element) {
        return map.get(element);
    }
}

You pass a Page Object as an argument, @Name annotation is read and stored into the HashMap<WebElement, String>

Initialize name storing in a constructor:

    public PageObject(WebDriver driver) {
        PageFactory.initElements(driver, this);
        NameStore.store(this); //THIS IS CRUCIAL. Also make sure that's always AFTER PageFactory initialization
    }

And now, to update your click method:

public static void click(WebElement element) throws Throwable {
        try {
            element.click();
        } catch(ElementNotInteractableException E1) {
            throw new UnableToInteractWithElementException("Unable To Interact With " + NameStore.get(element));
        }
    }

Hope it helps!

Disclaimer: I didn't test it in the context of multi-threading. I did not test in the context of a constructor.

Upvotes: 2

Spencer Melo
Spencer Melo

Reputation: 410

Hi I'm not the pro with reflection, I'm not sure if I did get the question right. You may have some another optimal way of doing it, but follow one solution, create a method that receives the WebElement and return the description:

private static String getDescription(WebElement element) throws IllegalAccessException {
    //Get all the fields of the page object.
    for (Field field : YOUR_PAGE_OBJECT.class.getDeclaredFields()) {
        Name name = field.getAnnotation(Name.class);
        //Consider only the ones that is annotated by your @Name
        if (name != null) {
            WebElement classElement = (WebElement) field.get(element);
            //Get the element from the class and compare with your current.
            if (classElement != null && (classElement).equals(element)) {
                return name.Description();
            }
        }
    }
    return ":C"; // or throw something, whatever you want...
}

Your code will end like:

public static void click(WebElement element) throws Throwable {
        try {
            element.click();
        } catch(ElementNotInteractableException E1) {
            throw new UnableToInteractWithElementException("Unable To Interact With " + getDescription(element));
        }
    }

Upvotes: 1

Related Questions