Open emaks opened 9 years ago
Can you please link the code line you're talking about here?
In that line the HtmlElementDecorator
is used, but that's ok because the locator factory you've given is still used. Isn't it?
no, instance of new HtmlElementDecorator(elementToWrap)
will be initialized using
public HtmlElementDecorator(SearchContext searchContext) {
this(new HtmlElementLocatorFactory(searchContext));
}
constructor where default ElementLocatorFactory
is used
In that case, child elements will be initialized by using default(HtmlElementLocatorFactory
) class. but, I expect them to be initialized by using class from parent(NotDefaultElementLocatorFactory
)
yep, i see now, that's a tricky piece... can you please share a problem you are trying to solve with custom ElementLocatorFactory? so i can write the test and propose solution? or you can write failing test describing your problem and pull request it.
I want to add possibility to change timeOutInSeconds parameter
If I remember correctly there was a task for making that value configurable on per-element basis.
yep, here is abandoned PR https://github.com/yandex-qatools/htmlelements/pull/63, you can fix all the problems here and we'll merge it
please, see #82
@artkoshelev are there any updates in #82?
@emaks will look at it in a few days, thank you
I was just about to raise an issue on this myself as have struck the same problem on work related to issue #56. Any idea when a new version might be released?
@andrew-sumner which issue you are talking about?
@artkoshelev
I have a custom decorator called that I use to initialise the page elements but this decorator is not passed onto inner elements.
PageFactory.initElements(new PageObjectAwareHtmlElementDecorator(new HtmlElementLocatorFactory(driver), this), this);
I've ended up working around the issue and have included the code so you can see what I've had to do to implement this.
It consist of two classes:
PageObjectAwareHtmlElementsLoader: this class allows me to create HtmlElements components on the fly rather than relying on fields.
List<WageDeclarationsComponent> taskHistoryRecords = new PageObjectAwareHtmlElementsLoader(driver, this)
.findElements(WageDeclarationsComponent.class, By.cssSelector(TASK_HISTORY_SELECTOR));
The web app that I am testing is, well bloody awful to be honest. It has up to four levels of nested iframes and is written using Dojo so is heavy with Ajax requests and I've found I've had to be very selective around where I create some of the components to avoid StaleElementReference and NoSuchElement exceptions.
I have two remaining issues with my implementation of the decorator that you may be able to help with:
I am having problems with lazy loading of these components.
My suspicion is that by setting the driver/pageobject or the fact that the components often contain lists then one of these conditions is forcing the elements to be loaded. Do you have any idea what may be forcing the load? And for bonus points: a solution?
NOTE: I haven't looked at TypifiedElements as I don't use those much.
PageObjectAwareHtmlElementDecorator
package nz.govt.msd.driver.web.pagefactory;
import static nz.govt.msd.driver.web.pagefactory.PageObjectAwareHtmlElementsLoader.createHtmlElement;
import static ru.yandex.qatools.htmlelements.utils.HtmlElementUtils.getElementName;
import java.lang.reflect.Field;
import java.util.List;
import org.openqa.selenium.WebElement;
import nz.govt.msd.driver.web.BasePageObject;
import ru.yandex.qatools.htmlelements.element.HtmlElement;
import ru.yandex.qatools.htmlelements.loader.decorator.HtmlElementDecorator;
import ru.yandex.qatools.htmlelements.pagefactory.CustomElementLocatorFactory;
/**
* A custom implementation of the {@link HtmlElementDecorator} that supports the
* {@link WebDriverAware} and {@link PageObjectAware} interfaces.
*
* @author Andrew Sumner
*/
public class PageObjectAwareHtmlElementDecorator extends HtmlElementDecorator {
private BasePageObject<?> pageObject;
/**
* Constructor.
*
* @param factory Element locator factory
* @param pageObject PageObject being decorated
*/
public PageObjectAwareHtmlElementDecorator(CustomElementLocatorFactory factory, BasePageObject<?> pageObject) {
super(factory);
this.pageObject = pageObject;
}
@SuppressWarnings("unchecked")
@Override
protected <T extends HtmlElement> T decorateHtmlElement(ClassLoader loader, Field field) {
WebElement elementToWrap = decorateWebElement(loader, field);
// Calling our custom createHtmlElement method rather than the one supplied by Yandex
return createHtmlElement((Class<T>) field.getType(), elementToWrap, getElementName(field), pageObject);
}
// TODO Will need to implement our own version of decorateHtmlElementList() in the same manner
// that we have for decorateHtmlElement() so using PageObjectAwareHtmlElementDecorator
@Override
protected <T extends HtmlElement> List<T> decorateHtmlElementList(ClassLoader loader, Field field) {
List<T> list = super.decorateHtmlElementList(loader, field);
if (!list.isEmpty()) {
if (list.get(0) instanceof WebDriverAware || list.get(0) instanceof PageObjectAware) {
for (T element : list) {
setAwareValue(element);
}
}
}
return list;
}
private void setAwareValue(HtmlElement element) {
if (element instanceof WebDriverAware) {
((WebDriverAware)element).setWebDriver(pageObject.getBrowser().getDriver());
}
if (element instanceof PageObjectAware) {
((PageObjectAware)element).setPageObject(pageObject);
}
}
}
PageObjectAwareHtmlElementsLoader
package nz.govt.msd.driver.web.pagefactory;
import static ru.yandex.qatools.htmlelements.utils.HtmlElementUtils.getElementName;
import static ru.yandex.qatools.htmlelements.utils.HtmlElementUtils.newInstance;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.LinkedList;
import java.util.List;
import org.openqa.selenium.By;
import org.openqa.selenium.SearchContext;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.internal.WrapsDriver;
import org.openqa.selenium.support.PageFactory;
import nz.govt.msd.driver.web.BasePageObject;
import ru.yandex.qatools.htmlelements.element.HtmlElement;
import ru.yandex.qatools.htmlelements.exceptions.HtmlElementsException;
import ru.yandex.qatools.htmlelements.loader.decorator.HtmlElementLocatorFactory;
/**
* A helper class for finding custom HtmlElement(s) in the same way that driver.findElement() and driver.findElements() methods work.
*
*
* @author Andrew Sumner
*/
public class PageObjectAwareHtmlElementsLoader implements WrapsDriver {
private final WebDriver driver;
private BasePageObject<?> pageObject;
/**
* Constructor.
*
* @param driver WebDriver
*/
public PageObjectAwareHtmlElementsLoader(WebDriver driver, BasePageObject<?> pageObject) {
this.driver = driver;
this.pageObject = pageObject;
}
@Override
public WebDriver getWrappedDriver() {
return driver;
}
/**
* Get the named field from the class.
*
* @param elementClass Class that field belongs to
* @param fieldName Name of field to find
* @return Field if found, or null if not found.
*/
public Field getFieldFromClass(Class<?> elementClass, String fieldName) {
try {
return elementClass.getDeclaredField(fieldName);
} catch (Exception e) {
return null;
}
}
/**
* Find all elements within the current page using the given mechanism.
* This method is affected by the 'implicit wait' times in force at the time of execution. When
* implicitly waiting, this method will return as soon as there are more than 0 items in the
* found collection, or will return an empty list if the timeout is reached.
*
* @param <T> A class that extends HtmlElement
* @param elementClass The specific HtmlElement class wrapping the element
* @param by The locating mechanism to use
* @return A list of all {@link WebElement}s, or an empty list if nothing matches
* @see org.openqa.selenium.By
* @see org.openqa.selenium.WebDriver.Timeouts
*/
public <T extends HtmlElement> List<T> findElements(Class<T> elementClass, By by) {
return findElements(elementClass, by, null);
}
/**
* Find all elements within the current page using the given mechanism.
* This method is affected by the 'implicit wait' times in force at the time of execution. When
* implicitly waiting, this method will return as soon as there are more than 0 items in the
* found collection, or will return an empty list if the timeout is reached.
*
* @param <T> A class that extends HtmlElement
* @param elementClass The specific HtmlElement class wrapping the element
* @param by The locating mechanism to use
* @param field the field that is being updated - used to get the @name annotation
* @return A list of all {@link WebElement}s, or an empty list if nothing matches
* @see org.openqa.selenium.By
* @see org.openqa.selenium.WebDriver.Timeouts
*/
public <T extends HtmlElement> List<T> findElements(Class<T> elementClass, By by, Field field) {
String name = null;
if (field != null) {
name = getElementName(field);
}
List<T> elements = new LinkedList<T>();
for (WebElement element : driver.findElements(by)) {
elements.add(createHtmlElement(elementClass, element, name, pageObject));
}
return elements;
}
/**
* Find the first {@link WebElement} using the given method.
* This method is affected by the 'implicit wait' times in force at the time of execution.
* The findElement(..) invocation will return a matching row, or try again repeatedly until
* the configured timeout is reached.
*
* findElement should not be used to look for non-present elements, use {@link #findElements(By)}
* and assert zero length response instead.
*
* @param <T> A class that extends HtmlElement
* @param elementClass The specific HtmlElement class wrapping the element
* @param by The locating mechanism
* @return The first matching element on the current page
* @throws org.openqa.selenium.NoSuchElementException If no matching elements are found
* @see org.openqa.selenium.By
* @see org.openqa.selenium.WebDriver.Timeouts
*/
public <T extends HtmlElement> T findElement(Class<T> elementClass, By by) {
WebElement element = driver.findElement(by);
return createHtmlElement(elementClass, element, null, pageObject);
}
/**
* Creates an instance of the given class representing a block of elements and initializes its fields
* with lazy proxies.
*
* Is a COPY of the Yandex HtmlElementLoader classes method so that replace
* the call to populatePageObject() with one that supports our page decorator.
*
* @param <T> A class to be instantiated and initialized.
* @param elementClass A class to be instantiated and initialized.
* @param elementToWrap WebElement to wrap
* @param name Name of element
* @return Initialised instance of the specified class.
*/
public static <T extends HtmlElement> T createHtmlElement(Class<T> elementClass, WebElement elementToWrap, String name, BasePageObject<?> parentPageObject) {
try {
T instance = newInstance(elementClass);
instance.setWrappedElement(elementToWrap);
instance.setName(name);
// Add reference to pageobject / browser for those classes that want them
setAwareValue(instance, parentPageObject);
// Recursively initialize elements of the block
populatePageObject(instance, elementToWrap, parentPageObject);
return instance;
} catch (NoSuchMethodException | InstantiationException | IllegalAccessException
| InvocationTargetException e) {
throw new HtmlElementsException(e);
}
}
/**
* Initialises fields of the given HtmlElements object using specified locator factory.
*
* @param page Page object to be initialised.
* @param locatorFactory Locator factory that will be used to locate elements.
*/
private static void populatePageObject(Object instance, SearchContext searchContext, BasePageObject<?> parentPageObject) {
PageFactory.initElements(new PageObjectAwareHtmlElementDecorator(new HtmlElementLocatorFactory(searchContext), parentPageObject), instance);
}
private static void setAwareValue(HtmlElement element, BasePageObject<?> parentPageObject) {
if (element instanceof WebDriverAware) {
((WebDriverAware) element).setWebDriver(parentPageObject.getBrowser().getDriver());
}
if (element instanceof PageObjectAware) {
((PageObjectAware) element).setPageObject(parentPageObject);
}
}
}
@andrew-sumner Have you succeeded to overcome these issues? I am trying to make the solution #56 work, however, I am facing with same problems that you have mentioned above. So, I would be very grateful for any assistance or solution from your side.
I have, I’ll post a link to the code tomorrow.
On 27/08/2019, at 7:33 AM, IZaiarnyi notifications@github.com<mailto:notifications@github.com> wrote:
@andrew-sumnerhttps://github.com/andrew-sumner Have you succeeded to overcome these issues? I am trying to make the solution #56 https://github.com/yandex-qatools/htmlelements/issues/56#issuecomment-190935126 works, however, I am facing with same problems that you have mentioned above. So, I would be very grateful for any assistance or solution from your side.
— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHubhttps://github.com/yandex-qatools/htmlelements/issues/78?email_source=notifications&email_token=ACB74KBJDREN43ZC5HAMHKDQGQVZTA5CNFSM4A7BJ2N2YY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOD5FNQXQ#issuecomment-524998750, or mute the threadhttps://github.com/notifications/unsubscribe-auth/ACB74KBLYXOL3MB7MP63QKDQGQVZTANCNFSM4A7BJ2NQ.
This is my impletentaion, not sure if it will help you or not. I've been meaning to try to get these merged back into this project but haven't yet looked to see if I can maked it generic enough and other things always take priority...
Thank you, it has helped me a lot. This functionality is definitely worth to be moved into htmlelements project.
This is my impletentaion, not sure if it will help you or not. I've been meaning to try to get these merged back into this project but haven't yet looked to see if I can maked it generic enough and other things always take priority...
when I try to initialize page elements with custom ElementLocatorFactory, like
in
private <T extends HtmlElement> T decorateHtmlElement(Class<T> elementClass, ClassLoader loader, ElementLocator locator, String elementName) { }
function whenPageFactory.initElements(new HtmlElementDecorator(elementToWrap), htmlElementInstance);
line is executed will be used default HtmlElementLocatorFactory class