Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import aquality.selenium.core.configurations.*;
import aquality.selenium.core.elements.IElementsModule;
import aquality.selenium.core.elements.interfaces.IElementFactory;
import aquality.selenium.core.elements.interfaces.IElementFinder;
import aquality.selenium.core.localization.ILocalizationManager;
import aquality.selenium.core.localization.ILocalizationModule;
Expand Down Expand Up @@ -45,5 +46,6 @@ protected void configure() {
bind(ILocalizedLogger.class).to(getLocalizedLoggerImplementation()).in(Singleton.class);
bind(IConditionalWait.class).to(getConditionalWaitImplementation());
bind(IElementFinder.class).to(getElementFinderImplementation());
bind(IElementFactory.class).to(getElementFactoryImplementation());
}
}
165 changes: 165 additions & 0 deletions src/main/java/aquality/selenium/core/elements/ElementFactory.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
package aquality.selenium.core.elements;

import aquality.selenium.core.elements.interfaces.IElement;
import aquality.selenium.core.elements.interfaces.IElementFactory;
import aquality.selenium.core.elements.interfaces.IElementFinder;
import aquality.selenium.core.elements.interfaces.IElementSupplier;
import aquality.selenium.core.localization.ILocalizationManager;
import aquality.selenium.core.logging.Logger;
import aquality.selenium.core.waitings.IConditionalWait;
import com.google.inject.Inject;
import org.openqa.selenium.By;
import org.openqa.selenium.By.ByXPath;
import org.openqa.selenium.InvalidArgumentException;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.pagefactory.ByChained;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeoutException;

public class ElementFactory implements IElementFactory {

private static final int XPATH_SUBSTRING_BEGIN_INDEX = 10;
private static final long ZERO_TIMEOUT = 0L;

private final IConditionalWait conditionalWait;
private final IElementFinder elementFinder;
private final ILocalizationManager localizationManager;

@Inject
public ElementFactory(IConditionalWait conditionalWait, IElementFinder elementFinder, ILocalizationManager localizationManager) {
this.conditionalWait = conditionalWait;
this.elementFinder = elementFinder;
this.localizationManager = localizationManager;
}

@Override
public <T extends IElement> T getCustomElement(IElementSupplier<T> elementSupplier, By locator, String name, ElementState state) {
return elementSupplier.get(locator, name, state);
}

@Override
public <T extends IElement> T getCustomElement(Class<T> clazz, By locator, String name, ElementState state) {
IElementSupplier<T> elementSupplier = getDefaultElementSupplier(clazz);
return getCustomElement(elementSupplier, locator, name, state);
}

@Override
public <T extends IElement> T findChildElement(IElement parentElement, By childLoc, String name, Class<T> clazz, ElementState state) {
IElementSupplier<T> elementSupplier = getDefaultElementSupplier(clazz);
return findChildElement(parentElement, childLoc, name, elementSupplier, state);
}

@Override
public <T extends IElement> T findChildElement(IElement parentElement, By childLoc, String name, IElementSupplier<T> supplier, ElementState state) {
String childName = name == null ? "Child element of ".concat(parentElement.getName()) : name;
By fullLocator = new ByChained(parentElement.getLocator(), childLoc);
return supplier.get(fullLocator, childName, state);
}

@Override
public <T extends IElement> List<T> findElements(By locator, String name, IElementSupplier<T> supplier,
ElementsCount count, ElementState state) {
try {
waitForElementsCount(locator, count, state);
} catch (TimeoutException e) {
throw new org.openqa.selenium.TimeoutException(e.getMessage());
}
List<WebElement> webElements = elementFinder.findElements(locator, state, ZERO_TIMEOUT);
String namePrefix = name == null ? "element" : name;
List<T> list = new ArrayList<>();
for (int index = 1; index <= webElements.size(); index++) {
WebElement webElement = webElements.get(index - 1);
String currentName = String.format("%1$s %2$s", namePrefix, index);
T element = supplier.get(generateXpathLocator(locator, webElement, index), currentName, state);
list.add(element);
}
return list;
}

protected void waitForElementsCount(By locator, ElementsCount count, ElementState state) throws TimeoutException {
switch (count) {
case ZERO:
conditionalWait.waitForTrue(() -> elementFinder.findElements(locator, state, ZERO_TIMEOUT).isEmpty(),
localizationManager.getLocalizedMessage("loc.elements.found.but.should.not",
locator.toString(), state.toString()));
break;
case MORE_THEN_ZERO:
conditionalWait.waitForTrue(() -> !elementFinder.findElements(locator, state, ZERO_TIMEOUT).isEmpty(),
localizationManager.getLocalizedMessage("loc.no.elements.found.by.locator",
locator.toString(), state.toString()));
break;
case ANY:
conditionalWait.waitFor(() -> elementFinder.findElements(locator, state, ZERO_TIMEOUT) != null);
break;
default:
throw new IllegalArgumentException("No such expected value: ".concat(count.toString()));
}
}

@Override
public <T extends IElement> List<T> findElements(By locator, String name, Class<T> clazz,
ElementsCount count, ElementState state) {
IElementSupplier<T> elementSupplier = getDefaultElementSupplier(clazz);
return findElements(locator, name, elementSupplier, count, state);
}

/**
* Generates xpath locator for target element.
*
* @param multipleElementsLocator locator used to find elements. Currently only {@link ByXPath} locators are supported.
* @param webElement target element.
* @param elementIndex index of target element.
* @return target element's locator
*/
protected By generateXpathLocator(By multipleElementsLocator, WebElement webElement, int elementIndex) {
Class supportedLocatorType = ByXPath.class;
if (multipleElementsLocator.getClass().equals(supportedLocatorType)) {
return By.xpath(
String.format("(%1$s)[%2$s]", multipleElementsLocator.toString().substring(XPATH_SUBSTRING_BEGIN_INDEX), elementIndex));
}
throw new InvalidArgumentException(String.format(
"Cannot define unique baseLocator for element %1$s. Multiple elements' baseLocator %2$s is not %3$s, and is not supported yet",
webElement.toString(), multipleElementsLocator, supportedLocatorType));
}

/**
* Gets map between elements interfaces and their implementations.
* Can be extended for custom elements with custom interfaces.
*
* @return Map where key is interface and value is its implementation.
*/
protected Map<Class<? extends IElement>, Class<? extends IElement>> getElementTypesMap() {
return new HashMap<>();
}

protected <T extends IElement> Class<T> resolveElementClass(Class<T> clazz) {
if (clazz.isInterface() && !getElementTypesMap().containsKey(clazz)) {
throw new IllegalArgumentException(
String.format("Interface %1$s is not found in getElementTypesMap()", clazz));
}

return clazz.isInterface() ? (Class<T>) getElementTypesMap().get(clazz) : clazz;
}

protected <T extends IElement> IElementSupplier<T> getDefaultElementSupplier(Class<T> clazz) {
return (locator, name, state) -> {
try {
Constructor<T> ctor = resolveElementClass(clazz)
.getDeclaredConstructor(By.class, String.class, ElementState.class);
ctor.setAccessible(true);
T instance = ctor.newInstance(locator, name, state);
ctor.setAccessible(false);
return instance;
} catch (NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException e) {
Logger.getInstance().debug(e.getMessage());
throw new IllegalArgumentException("Something went wrong during element casting");
}
};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package aquality.selenium.core.elements;

public enum ElementsCount {
ZERO,
MORE_THEN_ZERO,
ANY
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package aquality.selenium.core.elements;

import aquality.selenium.core.elements.interfaces.IElementFactory;
import aquality.selenium.core.elements.interfaces.IElementFinder;

/**
Expand All @@ -12,4 +13,11 @@ public interface IElementsModule {
default Class<? extends IElementFinder> getElementFinderImplementation() {
return ElementFinder.class;
}

/**
* @return class which implements IElementFactory
*/
default Class<? extends IElementFactory> getElementFactoryImplementation() {
return ElementFactory.class;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package aquality.selenium.core.elements.interfaces;

import org.openqa.selenium.By;
import org.openqa.selenium.remote.RemoteWebElement;

public interface IElement extends IParent {

/**
* Gets clear WebElement.
*
* @return WebElement
*/
default RemoteWebElement getElement() {
return getElement(null);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what does initializing with null mean?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it means that the timeout wasn't specified and we will use the default one

}

/**
* Gets clear WebElement.
*
* @param timeout Timeout for waiting
* @return WebElement
*/
RemoteWebElement getElement(Long timeout);

/**
* Gets element name.
*
* @return name
*/
String getName();

/**
* Gets element locator.
*
* @return locator
*/
By getLocator();
}
Loading