Skip to content

Commit 6a4e8fa

Browse files
authored
Implemented ElementFactory (#31)
* Implemented ElementFactory * Refactor ElementFactory: resolve sonar and code review issues * add methods to IElementFactory interface, reworked implementation, add tests
1 parent 31bbb9a commit 6a4e8fa

File tree

15 files changed

+856
-0
lines changed

15 files changed

+856
-0
lines changed

src/main/java/aquality/selenium/core/applications/AqualityModule.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import aquality.selenium.core.configurations.*;
44
import aquality.selenium.core.elements.IElementsModule;
5+
import aquality.selenium.core.elements.interfaces.IElementFactory;
56
import aquality.selenium.core.elements.interfaces.IElementFinder;
67
import aquality.selenium.core.localization.ILocalizationManager;
78
import aquality.selenium.core.localization.ILocalizationModule;
@@ -45,5 +46,6 @@ protected void configure() {
4546
bind(ILocalizedLogger.class).to(getLocalizedLoggerImplementation()).in(Singleton.class);
4647
bind(IConditionalWait.class).to(getConditionalWaitImplementation());
4748
bind(IElementFinder.class).to(getElementFinderImplementation());
49+
bind(IElementFactory.class).to(getElementFactoryImplementation());
4850
}
4951
}
Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
package aquality.selenium.core.elements;
2+
3+
import aquality.selenium.core.elements.interfaces.IElement;
4+
import aquality.selenium.core.elements.interfaces.IElementFactory;
5+
import aquality.selenium.core.elements.interfaces.IElementFinder;
6+
import aquality.selenium.core.elements.interfaces.IElementSupplier;
7+
import aquality.selenium.core.localization.ILocalizationManager;
8+
import aquality.selenium.core.logging.Logger;
9+
import aquality.selenium.core.waitings.IConditionalWait;
10+
import com.google.inject.Inject;
11+
import org.openqa.selenium.By;
12+
import org.openqa.selenium.By.ByXPath;
13+
import org.openqa.selenium.InvalidArgumentException;
14+
import org.openqa.selenium.WebElement;
15+
import org.openqa.selenium.support.pagefactory.ByChained;
16+
17+
import java.lang.reflect.Constructor;
18+
import java.lang.reflect.InvocationTargetException;
19+
import java.util.ArrayList;
20+
import java.util.HashMap;
21+
import java.util.List;
22+
import java.util.Map;
23+
import java.util.concurrent.TimeoutException;
24+
25+
public class ElementFactory implements IElementFactory {
26+
27+
private static final int XPATH_SUBSTRING_BEGIN_INDEX = 10;
28+
private static final long ZERO_TIMEOUT = 0L;
29+
30+
private final IConditionalWait conditionalWait;
31+
private final IElementFinder elementFinder;
32+
private final ILocalizationManager localizationManager;
33+
34+
@Inject
35+
public ElementFactory(IConditionalWait conditionalWait, IElementFinder elementFinder, ILocalizationManager localizationManager) {
36+
this.conditionalWait = conditionalWait;
37+
this.elementFinder = elementFinder;
38+
this.localizationManager = localizationManager;
39+
}
40+
41+
@Override
42+
public <T extends IElement> T getCustomElement(IElementSupplier<T> elementSupplier, By locator, String name, ElementState state) {
43+
return elementSupplier.get(locator, name, state);
44+
}
45+
46+
@Override
47+
public <T extends IElement> T getCustomElement(Class<T> clazz, By locator, String name, ElementState state) {
48+
IElementSupplier<T> elementSupplier = getDefaultElementSupplier(clazz);
49+
return getCustomElement(elementSupplier, locator, name, state);
50+
}
51+
52+
@Override
53+
public <T extends IElement> T findChildElement(IElement parentElement, By childLoc, String name, Class<T> clazz, ElementState state) {
54+
IElementSupplier<T> elementSupplier = getDefaultElementSupplier(clazz);
55+
return findChildElement(parentElement, childLoc, name, elementSupplier, state);
56+
}
57+
58+
@Override
59+
public <T extends IElement> T findChildElement(IElement parentElement, By childLoc, String name, IElementSupplier<T> supplier, ElementState state) {
60+
String childName = name == null ? "Child element of ".concat(parentElement.getName()) : name;
61+
By fullLocator = new ByChained(parentElement.getLocator(), childLoc);
62+
return supplier.get(fullLocator, childName, state);
63+
}
64+
65+
@Override
66+
public <T extends IElement> List<T> findElements(By locator, String name, IElementSupplier<T> supplier,
67+
ElementsCount count, ElementState state) {
68+
try {
69+
waitForElementsCount(locator, count, state);
70+
} catch (TimeoutException e) {
71+
throw new org.openqa.selenium.TimeoutException(e.getMessage());
72+
}
73+
List<WebElement> webElements = elementFinder.findElements(locator, state, ZERO_TIMEOUT);
74+
String namePrefix = name == null ? "element" : name;
75+
List<T> list = new ArrayList<>();
76+
for (int index = 1; index <= webElements.size(); index++) {
77+
WebElement webElement = webElements.get(index - 1);
78+
String currentName = String.format("%1$s %2$s", namePrefix, index);
79+
T element = supplier.get(generateXpathLocator(locator, webElement, index), currentName, state);
80+
list.add(element);
81+
}
82+
return list;
83+
}
84+
85+
protected void waitForElementsCount(By locator, ElementsCount count, ElementState state) throws TimeoutException {
86+
switch (count) {
87+
case ZERO:
88+
conditionalWait.waitForTrue(() -> elementFinder.findElements(locator, state, ZERO_TIMEOUT).isEmpty(),
89+
localizationManager.getLocalizedMessage("loc.elements.found.but.should.not",
90+
locator.toString(), state.toString()));
91+
break;
92+
case MORE_THEN_ZERO:
93+
conditionalWait.waitForTrue(() -> !elementFinder.findElements(locator, state, ZERO_TIMEOUT).isEmpty(),
94+
localizationManager.getLocalizedMessage("loc.no.elements.found.by.locator",
95+
locator.toString(), state.toString()));
96+
break;
97+
case ANY:
98+
conditionalWait.waitFor(() -> elementFinder.findElements(locator, state, ZERO_TIMEOUT) != null);
99+
break;
100+
default:
101+
throw new IllegalArgumentException("No such expected value: ".concat(count.toString()));
102+
}
103+
}
104+
105+
@Override
106+
public <T extends IElement> List<T> findElements(By locator, String name, Class<T> clazz,
107+
ElementsCount count, ElementState state) {
108+
IElementSupplier<T> elementSupplier = getDefaultElementSupplier(clazz);
109+
return findElements(locator, name, elementSupplier, count, state);
110+
}
111+
112+
/**
113+
* Generates xpath locator for target element.
114+
*
115+
* @param multipleElementsLocator locator used to find elements. Currently only {@link ByXPath} locators are supported.
116+
* @param webElement target element.
117+
* @param elementIndex index of target element.
118+
* @return target element's locator
119+
*/
120+
protected By generateXpathLocator(By multipleElementsLocator, WebElement webElement, int elementIndex) {
121+
Class supportedLocatorType = ByXPath.class;
122+
if (multipleElementsLocator.getClass().equals(supportedLocatorType)) {
123+
return By.xpath(
124+
String.format("(%1$s)[%2$s]", multipleElementsLocator.toString().substring(XPATH_SUBSTRING_BEGIN_INDEX), elementIndex));
125+
}
126+
throw new InvalidArgumentException(String.format(
127+
"Cannot define unique baseLocator for element %1$s. Multiple elements' baseLocator %2$s is not %3$s, and is not supported yet",
128+
webElement.toString(), multipleElementsLocator, supportedLocatorType));
129+
}
130+
131+
/**
132+
* Gets map between elements interfaces and their implementations.
133+
* Can be extended for custom elements with custom interfaces.
134+
*
135+
* @return Map where key is interface and value is its implementation.
136+
*/
137+
protected Map<Class<? extends IElement>, Class<? extends IElement>> getElementTypesMap() {
138+
return new HashMap<>();
139+
}
140+
141+
protected <T extends IElement> Class<T> resolveElementClass(Class<T> clazz) {
142+
if (clazz.isInterface() && !getElementTypesMap().containsKey(clazz)) {
143+
throw new IllegalArgumentException(
144+
String.format("Interface %1$s is not found in getElementTypesMap()", clazz));
145+
}
146+
147+
return clazz.isInterface() ? (Class<T>) getElementTypesMap().get(clazz) : clazz;
148+
}
149+
150+
protected <T extends IElement> IElementSupplier<T> getDefaultElementSupplier(Class<T> clazz) {
151+
return (locator, name, state) -> {
152+
try {
153+
Constructor<T> ctor = resolveElementClass(clazz)
154+
.getDeclaredConstructor(By.class, String.class, ElementState.class);
155+
ctor.setAccessible(true);
156+
T instance = ctor.newInstance(locator, name, state);
157+
ctor.setAccessible(false);
158+
return instance;
159+
} catch (NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException e) {
160+
Logger.getInstance().debug(e.getMessage());
161+
throw new IllegalArgumentException("Something went wrong during element casting");
162+
}
163+
};
164+
}
165+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package aquality.selenium.core.elements;
2+
3+
public enum ElementsCount {
4+
ZERO,
5+
MORE_THEN_ZERO,
6+
ANY
7+
}

src/main/java/aquality/selenium/core/elements/IElementsModule.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package aquality.selenium.core.elements;
22

3+
import aquality.selenium.core.elements.interfaces.IElementFactory;
34
import aquality.selenium.core.elements.interfaces.IElementFinder;
45

56
/**
@@ -12,4 +13,11 @@ public interface IElementsModule {
1213
default Class<? extends IElementFinder> getElementFinderImplementation() {
1314
return ElementFinder.class;
1415
}
16+
17+
/**
18+
* @return class which implements IElementFactory
19+
*/
20+
default Class<? extends IElementFactory> getElementFactoryImplementation() {
21+
return ElementFactory.class;
22+
}
1523
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package aquality.selenium.core.elements.interfaces;
2+
3+
import org.openqa.selenium.By;
4+
import org.openqa.selenium.remote.RemoteWebElement;
5+
6+
public interface IElement extends IParent {
7+
8+
/**
9+
* Gets clear WebElement.
10+
*
11+
* @return WebElement
12+
*/
13+
default RemoteWebElement getElement() {
14+
return getElement(null);
15+
}
16+
17+
/**
18+
* Gets clear WebElement.
19+
*
20+
* @param timeout Timeout for waiting
21+
* @return WebElement
22+
*/
23+
RemoteWebElement getElement(Long timeout);
24+
25+
/**
26+
* Gets element name.
27+
*
28+
* @return name
29+
*/
30+
String getName();
31+
32+
/**
33+
* Gets element locator.
34+
*
35+
* @return locator
36+
*/
37+
By getLocator();
38+
}

0 commit comments

Comments
 (0)