Skip to content

Commit

Permalink
Feature/45 add find child elements to element (#51) +semver:feature
Browse files Browse the repository at this point in the history
* Implemented findChildElements in ElementFactory

* Add overloads to ElementFactory's findElements method 

* Add findChildElements methods to IParent and Element

* Implement xpath extraction logic in ElementFactory to be able to find multiple elements in non-web WebDriver-based libraries
  • Loading branch information
mialeska authored May 22, 2020
1 parent 000f9e2 commit fc42830
Show file tree
Hide file tree
Showing 9 changed files with 926 additions and 99 deletions.
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

<groupId>com.github.aquality-automation</groupId>
<artifactId>aquality-selenium-core</artifactId>
<version>1.0.2</version>
<version>1.1.0</version>

<packaging>jar</packaging>
<name>Aquality Selenium Core</name>
Expand Down
11 changes: 11 additions & 0 deletions src/main/java/aquality/selenium/core/elements/Element.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import org.openqa.selenium.remote.RemoteWebElement;

import java.time.Duration;
import java.util.List;
import java.util.function.Supplier;

public abstract class Element implements IElement {
Expand Down Expand Up @@ -128,6 +129,16 @@ public <T extends IElement> T findChildElement(By childLoc, String name, IElemen
return getElementFactory().findChildElement(this, childLoc, name, supplier, state);
}

@Override
public <T extends IElement> List<T> findChildElements(By childLoc, String name, Class<T> clazz, ElementState state, ElementsCount count) {
return getElementFactory().findChildElements(this, childLoc, name, clazz, count, state);
}

@Override
public <T extends IElement> List<T> findChildElements(By childLoc, String name, IElementSupplier<T> supplier, ElementState state, ElementsCount count) {
return getElementFactory().findChildElements(this, childLoc, name, supplier, count, state);
}

protected <T> T doWithRetry(Supplier<T> action) {
return getElementActionRetrier().doWithRetry(action);
}
Expand Down
77 changes: 71 additions & 6 deletions src/main/java/aquality/selenium/core/elements/ElementFactory.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import aquality.selenium.core.waitings.IConditionalWait;
import com.google.inject.Inject;
import org.openqa.selenium.By;
import org.openqa.selenium.By.ByTagName;
import org.openqa.selenium.By.ByXPath;
import org.openqa.selenium.InvalidArgumentException;
import org.openqa.selenium.WebElement;
Expand All @@ -26,6 +27,8 @@
public class ElementFactory implements IElementFactory {

private static final int XPATH_SUBSTRING_BEGIN_INDEX = 10;
private static final int TAGNAME_SUBSTRING_BEGIN_INDEX = 12;
private static final String TAGNAME_XPATH_PREFIX = "//";
private static final Duration ZERO_TIMEOUT = Duration.ZERO;

private final IConditionalWait conditionalWait;
Expand Down Expand Up @@ -59,10 +62,23 @@ public <T extends IElement> T findChildElement(IElement parentElement, By childL
@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);
By fullLocator = generateAbsoluteChildLocator(parentElement.getLocator(), childLoc);
return supplier.get(fullLocator, childName, state);
}

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

@Override
public <T extends IElement> List<T> findChildElements(IElement parentElement, By childLoc, String name, IElementSupplier<T> supplier, ElementsCount count, ElementState state) {
String childName = name == null ? "Child element of ".concat(parentElement.getName()) : name;
By fullLocator = generateAbsoluteChildLocator(parentElement.getLocator(), childLoc);
return findElements(fullLocator, childName, supplier, count, state);
}

@Override
public <T extends IElement> List<T> findElements(By locator, String name, IElementSupplier<T> supplier,
ElementsCount count, ElementState state) {
Expand Down Expand Up @@ -119,14 +135,63 @@ public <T extends IElement> List<T> findElements(By locator, String name, Class<
* @return target element's locator
*/
protected By generateXpathLocator(By multipleElementsLocator, WebElement webElement, int elementIndex) {
Class supportedLocatorType = ByXPath.class;
if (multipleElementsLocator.getClass().equals(supportedLocatorType)) {
if (isLocatorSupportedForXPathExtraction(multipleElementsLocator)) {
return By.xpath(
String.format("(%1$s)[%2$s]", multipleElementsLocator.toString().substring(XPATH_SUBSTRING_BEGIN_INDEX), elementIndex));
String.format("(%1$s)[%2$s]", extractXPathFromLocator(multipleElementsLocator), 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));
"Cannot define unique baseLocator for element %1$s. Multiple elements' baseLocator type %2$s is not supported yet",
webElement.toString(), multipleElementsLocator.getClass()));
}

/**
* Extracts XPath from passed locator.
* Current implementation works only with ByXPath.class and ByTagName locator types,
* but you can implement your own for the specific WebDriver type.
*
* @param locator locator to get xpath from.
* @return extracted XPath.
*/
protected String extractXPathFromLocator(By locator) {
Class supportedLocatorType = ByXPath.class;
if (locator.getClass().equals(supportedLocatorType)) {
return locator.toString().substring(XPATH_SUBSTRING_BEGIN_INDEX);
}
if (locator.getClass().equals(ByTagName.class)){
return TAGNAME_XPATH_PREFIX + locator.toString().substring(TAGNAME_SUBSTRING_BEGIN_INDEX);
}
throw new InvalidArgumentException(String.format(
"Cannot define xpath from locator %1$s. Locator type %2$s is not %3$s, and is not supported yet",
locator.toString(), locator.getClass(), supportedLocatorType));
}

/**
* Generates absolute child locator for target element.
*
* @param parentLoc parent locator
* @param childLoc child locator relative to parent
* @return absolute locator of the child
*/
protected By generateAbsoluteChildLocator(By parentLoc, By childLoc) {
if (isLocatorSupportedForXPathExtraction(parentLoc) && isLocatorSupportedForXPathExtraction(childLoc)) {
String childLocString = extractXPathFromLocator(childLoc);
String parentLocString = extractXPathFromLocator(parentLoc);
return By.xpath(parentLocString.concat(
childLocString.startsWith(".") ? childLocString.substring(1) : childLocString));
}
return new ByChained(parentLoc, childLoc);
}

/**
* Defines is the locator can be transformed to xpath or not.
* Current implementation works only with ByXPath.class and ByTagName locator types,
* but you can implement your own for the specific WebDriver type.
*
* @param locator locator to transform
* @return true if the locator can be transformed to xpath, false otherwise.
*/
protected boolean isLocatorSupportedForXPathExtraction(By locator) {
return locator.getClass().equals(ByXPath.class) || locator.getClass().equals(ByTagName.class);
}

/**
Expand Down
Loading

0 comments on commit fc42830

Please sign in to comment.