I have created a framework for automated testing that takes a Cucumber file and a page object and uses glue code to run automated tests. That glue code relies on reflection to instantiate elements on a web page in real time, like so:
/**
* Returns a PageElement object for a given elementName string from current page. Gets current page reference, replaces page name space characters qith '_'
*
* @param elementName String name of requested element
* @return PageElement the requested element
* @throws PageNotFoundException if no page object found or defined.
* @throws NoSuchElementException if element is not found or defined.
*/
public static PageElement getElement(String elementName) throws NoSuchElementException, PageNotFoundException {
Page page = PageManager.getPage();
elementName = elementName.replaceAll("\\s+", "_").toLowerCase();
Method pageElementName = null;
try { // Create a Method object to store the PageElementwe want to exercise;
pageElementName = page.getClass().getMethod(elementName);
} catch (NoSuchMethodException e) {
String errorMessage = StringUtils.format("Element {} is not defined for the page object {}. Make sure you have spelled the page object name correctly in your Cucumber step definition and in the page object.", elementName, page
.getClass().getSimpleName());
log.error(errorMessage);
throw new NoSuchElementException(errorMessage, e);
}
PageElement element = null;
try { // Invoke the creation of the PageElement and return it into a variable if no exception is thrown.
element = (PageElement) pageElementName.invoke(page);
log.trace("PageElement Name: " + pageElementName.getName());
} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
String errorMessage = StringUtils.format("PageElement {} could not be found on Page {}. Please ensure the element is defined on the page.", pageElementName.getName(), page.getName());
log.error(errorMessage);
throw new NoSuchElementException(errorMessage);
}
return element;
}
If I need to return something like a drop down object which has more methods than a standard object, I do so by using a method that casts the returned object:
/**
* Returns the Dropdown associated with the element name on the currently active
* page.
*
* @param elementName String the name of the element to be returned
* @return Dropdown the dropdown associated with the element name on the currently active page
* @throws NoSuchElementException if the element cannot be found
* @throws PageNotFoundException if the page cannot be found
*/
public static Dropdown getElementAsDropdown(String elementName) throws SentinelException {
return (Dropdown) getElement(elementName);
}
Currently I have three drop down objects, and they all are defined differently inside of a page object:
public Dropdown state_dropdown() { return new Dropdown(ID, "state"); }
public MaterialUISelect age_dropdown() { return new MaterialUISelect(XPATH, "//*[@id=\"demo-simple-select\"]"); }
public PrimeNGDropdown city_dropdown() { return new PrimeNGDropdown(XPATH, "//p-dropdown[1]"); }
I want to be able to define all three drop downs in the Page object as Dropdown
and have the code figure out which one it is when the object is created. The reason for this is I want to make the page object creation as foolproof as possible, as most testers using the framework are not very technically savvy.
I feel like I need a factory method to do this, but I cannot figure out where it goes in my code. Does it go in the getElement code? Does it go in the Dropdown object code? Am I thinking about this all wrong and there is another solution I should be using? I'm at a loss despite having bounced this off a couple of other developers.