com.github.loyada.jdollarx.BasicPath Maven / Gradle / Ivy
package com.github.loyada.jdollarx;
import com.google.common.collect.ImmutableList;
import org.openqa.selenium.WebElement;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static com.github.loyada.jdollarx.ElementProperties.isInside;
import static com.github.loyada.jdollarx.PathUtils.hasHeirarchy;
import static com.github.loyada.jdollarx.PathUtils.oppositeRelation;
import static com.github.loyada.jdollarx.PathUtils.transformXpathToCorrectAxis;
import static java.lang.String.format;
public final class BasicPath implements Path {
private Optional insideXpath = Optional.empty();
private final Optional xpath;
private final Optional alternateXpath;
private final Optional xpathExplanation;
private final Optional describedBy;
private final Optional underlying;
private final ImmutableList elementProperties;
public static PathBuilder builder() {
return new PathBuilder();
}
public static final class PathBuilder {
private final Optional insideXpath;
private final Optional xpath;
private final Optional xpathExplanation;
private final Optional describedBy;
private final Optional underlying;
private final List elementProperties;
private final Optional alternateXpath;
public PathBuilder() {
insideXpath = Optional.empty();
xpath = Optional.empty();
alternateXpath = Optional.empty();
xpathExplanation = Optional.empty();
underlying = Optional.empty();
describedBy = Optional.empty();
elementProperties = Collections.emptyList();
}
public PathBuilder(Optional insideXpath,
Optional xpath,
Optional xpathExplanation,
Optional describedBy,
Optional underlying,
List elementProperties,
Optional alternateXpath
) {
this.insideXpath = insideXpath;
this.xpath = xpath;
this.xpathExplanation = xpathExplanation;
this.describedBy = describedBy;
this.underlying = underlying;
this.elementProperties = elementProperties;
this.alternateXpath = alternateXpath;
}
public PathBuilder withXpath(String xpath) {
return new PathBuilder(insideXpath, Optional.of(xpath), xpathExplanation, describedBy, underlying, elementProperties, alternateXpath);
}
public PathBuilder withAlternateXpath(String alternateXpath) {
return new PathBuilder(insideXpath, xpath, xpathExplanation, describedBy, underlying, elementProperties, Optional.of(alternateXpath));
}
public PathBuilder withInsideXpath(String insideXpath) {
return new PathBuilder(Optional.of(insideXpath), xpath, xpathExplanation, describedBy, underlying, elementProperties, alternateXpath);
}
public PathBuilder withXpathExplanation(String xpathExplanation) {
return new PathBuilder(insideXpath, xpath, Optional.of(xpathExplanation), describedBy, underlying, elementProperties, alternateXpath);
}
public PathBuilder withDescribedBy(String describedBy) {
return new PathBuilder(insideXpath, xpath, xpathExplanation, Optional.of(describedBy), underlying, elementProperties, alternateXpath);
}
public PathBuilder withUnderlying(WebElement underlying) {
return new PathBuilder(insideXpath, xpath, xpathExplanation, describedBy, Optional.of(underlying), elementProperties, alternateXpath);
}
public PathBuilder withInsideXpathOptional(Optional insideXpath) {
return new PathBuilder(insideXpath, xpath, xpathExplanation, describedBy, underlying, elementProperties, alternateXpath);
}
public PathBuilder withXpathOptional(Optional xpath) {
return new PathBuilder(insideXpath, xpath, xpathExplanation, describedBy, underlying, elementProperties, alternateXpath);
}
public PathBuilder withAlternateXpathOptional(Optional alternateXpath) {
return new PathBuilder(insideXpath, xpath, xpathExplanation, describedBy, underlying, elementProperties, alternateXpath);
}
public PathBuilder withXpathExplanationOptional(Optional xpathExplanation) {
return new PathBuilder(insideXpath, xpath, xpathExplanation, describedBy, underlying, elementProperties, alternateXpath);
}
public PathBuilder withDescribedByOptional(Optional describedBy) {
return new PathBuilder(insideXpath, xpath, xpathExplanation, describedBy, underlying, elementProperties, alternateXpath);
}
public PathBuilder withUnderlyingOptional(Optional underlying) {
return new PathBuilder(insideXpath, xpath, xpathExplanation, describedBy, underlying, elementProperties, alternateXpath);
}
public PathBuilder withElementProperties(List elementProperties) {
return new PathBuilder(insideXpath, xpath, xpathExplanation, describedBy, underlying, elementProperties, alternateXpath);
}
public BasicPath build() {
return new BasicPath(underlying, xpath, elementProperties, xpathExplanation, describedBy, insideXpath, alternateXpath);
}
}
private BasicPath(Optional underlying,
Optional xpath,
List elementProperties,
Optional xpathExplanation,
Optional describedBy,
Optional insideXpath,
Optional alternateXpath
) {
this.xpath = xpath;
this.xpathExplanation = xpathExplanation;
this.describedBy = describedBy;
this.underlying = underlying;
this.elementProperties = ImmutableList.copyOf(elementProperties);
this.insideXpath = insideXpath;
this.alternateXpath = alternateXpath;
}
//elements
public static final BasicPath element = builder().withXpath("*").withXpathExplanation("any element").build();
public static final BasicPath div = builder().withXpath("div").withXpathExplanation("div").build();
public static final BasicPath span = builder().withXpath("span").withXpathExplanation("span").build();
public static final BasicPath image = builder().withXpath("img").withXpathExplanation("image").build();
public static final BasicPath listItem = builder().withXpath("li").withXpathExplanation("list item").build();
public static final BasicPath button = builder().withXpath("button").withXpathExplanation("button").build();
public static final BasicPath unorderedList = builder().withXpath("ul").withXpathExplanation("unordered list").build();
public static final BasicPath input = builder().withXpath("input").withXpathExplanation("input").build();
public static final BasicPath anchor = builder().withXpath("a").withXpathExplanation("anchor").build();
public static final BasicPath form = builder().withXpath("form").withXpathExplanation("form").build();
public static final BasicPath iframe = builder().withXpath("iframe").withXpathExplanation("iframe").build();
public static final BasicPath html = builder().withXpath("html").withXpathExplanation("document").build();
public static final BasicPath body = builder().withXpath("body").withXpathExplanation("document body").build();
public static final BasicPath header1 = builder().withXpath("h1").withXpathExplanation("header-1").build();
public static final BasicPath header2 = builder().withXpath("h2").withXpathExplanation("header-2").build();
public static final BasicPath header3 = builder().withXpath("h3").withXpathExplanation("header-3").build();
public static final BasicPath header4 = builder().withXpath("h4").withXpathExplanation("header-4").build();
public static final BasicPath header5 = builder().withXpath("h5").withXpathExplanation("header-5").build();
public static final BasicPath header6 = builder().withXpath("h6").withXpathExplanation("header-6").build();
public static final BasicPath header = (BasicPath) header1.or(header2).or(header3).or(header4).or(header5).or(header6);
public static final BasicPath title = builder().withXpath("title").withXpathExplanation("title").build();
public static final BasicPath tr = builder().withXpath("tr").withXpathExplanation("table row").build();
public static final BasicPath td = builder().withXpath("td").withXpathExplanation("table cell").build();
public static final BasicPath th = builder().withXpath("th").withXpathExplanation("table header cell").build();
public static final BasicPath table = customElement("table");
public static final BasicPath select = builder().withXpath("select").withXpathExplanation("selection menu").build();
public static final BasicPath option = customElement("option");
public static final BasicPath paragraph = builder().withXpath("p").withXpathExplanation("paragraph").build();
public static BasicPath customElement(String el) {
return builder().withXpath(el).withXpathExplanation(el).build();
}
public static final class ChildNumber {
private final Integer n;
public ChildNumber(Integer n) {
this.n = n;
}
public Path ofType(Path path) {
String newXPath = path.getXPath().get() + format("[%d]", n);
String alternateXpath = path.getAlternateXPath().get() + format("[%d]", n);
return builder().withUnderlyingOptional(path.getUnderlyingSource()).
withXpath(newXPath).
withAlternateXpath(alternateXpath).
withXpathExplanation(format("child number %d of type(%s)", n, path)).build();
}
}
public static final class GlobalOccurrenceNumber {
private final Integer n;
public GlobalOccurrenceNumber(final Integer n) {
this.n = n;
}
public Path of(final Path path) {
final String prefix = (n == 1) ? "the first occurrence of " :
(n == 0) ? "the last occurrence of " : format("occurrence number %d of ", n);
final String pathString = path.toString();
final String wrapped = (pathString.contains(" ")) ? format("(%s)", pathString) : pathString;
final String index = (n == 0) ? "last()" : format("%d", n);
final String newXPath = format("(//%s)[%s]", path.getXPath().get(), index);
return builder().withUnderlyingOptional(path.getUnderlyingSource()).
withXpath(newXPath).withXpathExplanation(prefix + wrapped).build();
}
}
public static ChildNumber childNumber(Integer n) {
return new ChildNumber(n);
}
public static GlobalOccurrenceNumber occurrenceNumber(Integer n) {
return new GlobalOccurrenceNumber(n);
}
public static Path firstOccuranceOf(Path path) {
return path.withGlobalIndex(0);
}
public static Path lastOccuranceOf(Path path) {
return path.withGlobalIndex(-1);
}
@Override
public Optional getXPath() {
if (!xpath.isPresent() && elementProperties.isEmpty() && !insideXpath.isPresent()) {
return Optional.empty();
} else {
String processedXpath = (insideXpath.isPresent() ? (insideXpath.get() + "//") : "") + xpath.orElse("*");
String props = elementProperties.stream().map(e -> format("[%s]", e.toXpath())).
collect(Collectors.joining());
return Optional.of(processedXpath + props);
}
}
@Override
public Optional getAlternateXPath() {
if (!xpath.isPresent() && elementProperties.isEmpty() && !insideXpath.isPresent()) {
return Optional.empty();
} else {
String props = elementProperties.stream().map(e -> format("[%s]", e.toXpath())).
collect(Collectors.joining());
return Optional.of(alternateXpath.orElse(xpath.orElse("*")) + props);
}
}
private Optional getXPathWithoutInsideClause() {
if (!xpath.isPresent() && elementProperties.isEmpty()) {
return Optional.empty();
} else {
String props = elementProperties.stream().map(e -> format("[%s]", e.toXpath())).
collect(Collectors.joining());
return Optional.of(xpath.orElse("*") + props);
}
}
@Override
public Optional getUnderlyingSource() {
return this.underlying;
}
@Override
public Optional getXpathExplanation() {
return xpathExplanation;
}
@Override
public Optional getDescribedBy() {
return describedBy;
}
@Override
public List getElementProperties() {
return elementProperties;
}
@Override
public BasicPath describedBy(String description) {
return new BasicPath(underlying, xpath, elementProperties, xpathExplanation, Optional.of(description),
insideXpath, alternateXpath);
}
private void verifyRelationBetweenElements(Path path) {
if (path.getUnderlyingSource().isPresent() || !getXPath().isPresent() || !path.getXPath().isPresent())
throw new IllegalArgumentException();
}
@Override
public Path or(Path path) {
verifyRelationBetweenElements(path);
return builder().
withUnderlyingOptional(underlying).
withXpath(format("*[(self::%s) | (self::%s)]", transformXpathToCorrectAxis(this).get(),
transformXpathToCorrectAxis(path).get())).
withAlternateXpath(format("*[(self::%s) | (self::%s)]", getAlternateXPath().get(),
path.getAlternateXPath().get())).
withXpathExplanation(format("%s or %s", wrapIfNeeded(this), wrapIfNeeded(path))).
build();
}
@Override
public Path that(ElementProperty... prop) {
if (describedBy.isPresent()) {
return builder().withUnderlyingOptional(underlying).
withXpathOptional(getXPathWithoutInsideClause()).
withInsideXpathOptional(insideXpath).
withElementProperties(ImmutableList.copyOf(prop)).
withAlternateXpathOptional(alternateXpath).
withXpathExplanation(describedBy.get()).build();
} else {
ImmutableList newProps = ImmutableList.builder().
addAll(elementProperties).
addAll(Arrays.asList(prop)).
build();
return builder().withUnderlyingOptional(underlying).
withXpathOptional(xpath).
withInsideXpathOptional(insideXpath).
withElementProperties(newProps).
withDescribedByOptional(describedBy).
withAlternateXpathOptional(alternateXpath).
withXpathExplanationOptional(xpathExplanation).build();
}
}
@Override
public Path and(ElementProperty... prop) {
return that(prop);
}
@Override
public Path withText(String txt) {
return createNewWithAdditionalProperty(ElementProperties.hasText(txt));
}
@Override
public Path inside(final Path path) {
final String newXPath = getXPathWithoutInsideClause().orElse("");
final String correctedXpathForIndex;
final Optional correctInsidePath;
final String descriptionPrefix;
if (newXPath.startsWith("(")) {
correctedXpathForIndex = (newXPath + format("[%s]", isInside(path).toXpath()));
correctInsidePath = Optional.empty();
descriptionPrefix = ", and is inside ";
} else {
correctedXpathForIndex = newXPath;
correctInsidePath = Optional.of(path.getXPath().get() + (insideXpath.isPresent() ? "//" + insideXpath.get() : ""));
descriptionPrefix = ", inside ";
}
return builder().
withUnderlyingOptional(path.getUnderlyingSource()).
withXpath(correctedXpathForIndex).
withInsideXpathOptional(correctInsidePath).
withAlternateXpathOptional(this.that(ElementProperties.isDescendantOf(path)).getAlternateXPath()).
withXpathExplanation(toString() + descriptionPrefix + wrapIfNeeded(path)).
build();
}
@Override
public Path insideTopLevel() {
if (!getXPath().isPresent()) throw new IllegalArgumentException("must have a non-empty xpath");
return new PathBuilder().
withXpath(XpathUtils.insideTopLevel(getXPath().get())).
withDescribedBy(toString()).
build();
}
@Override
public Path afterSibling(Path path) {
return createWithHumanReadableRelation(path, "following-sibling", "after the sibling");
}
@Override
public BasicPath after(Path path) {
return createWithHumanReadableRelation(path, "following", "after");
}
@Override
public BasicPath beforeSibling(Path path) {
return createWithHumanReadableRelation(path, "preceding-sibling", "before the sibling");
}
@Override
public BasicPath before(Path path) {
return createWithHumanReadableRelation(path, "preceding", "before");
}
@Override
public Path childOf(Path path) {
return createWithSimpleRelation(path, "child");
}
@Override
public Path parentOf(Path path) {
return createWithSimpleRelation(path, "parent");
}
@Override
public Path containing(Path path) {
return ancestorOf(path);
}
@Override
public Path contains(Path path) {
return ancestorOf(path);
}
@Override
public Path ancestorOf(Path path) {
return createWithSimpleRelation(path, "ancestor");
}
@Override
public Path descendantOf(Path path) {
return createWithSimpleRelation(path, "descendant");
}
@Override
public Path withGlobalIndex(Integer n) {
return occurrenceNumber(n + 1).of(this);
}
@Override
public Path withClass(String cssClass) {
return createNewWithAdditionalProperty(ElementProperties.hasClass(cssClass));
}
@Override
public Path withClasses(String... cssClasses) {
return createNewWithAdditionalProperty(ElementProperties.hasClasses(cssClasses));
}
@Override
public Path withTextContaining(String txt) {
return createNewWithAdditionalProperty(ElementProperties.hasTextContaining(txt));
}
private Optional getXpathExplanationForToString() {
if (xpath.isPresent()) {
return xpathExplanation.isPresent() ?
xpathExplanation :
Optional.of("xpath: \"" + xpath.get() + "\"");
} else return Optional.empty();
}
private Optional getPropertiesToStringForLength1() {
String thatMaybe = (elementProperties.get(0).toString().startsWith("has") || elementProperties.get(0).toString().startsWith("is")) ? "that " : "";
return Optional.of(thatMaybe + elementProperties.get(0));
}
private Optional getPropertiesToStringForLengthLargerThan2() {
String propsAsList = elementProperties.stream().map(Object::toString).collect(Collectors.joining(", "));
if (xpathExplanation.isPresent() && xpathExplanation.get().contains("with properties") || elementProperties.size() == 1) {
return Optional.of("and " + propsAsList);
} else {
return Optional.of("that [" + propsAsList + "]");
}
}
@Override
public String toString() {
if (describedBy.isPresent() && !describedBy.equals(xpathExplanation)) {
return describedBy.get();
} else {
Optional underlyingOption = (underlying.isPresent()) ?
Optional.of("under reference element " + underlying.get()) :
Optional.empty();
Optional xpathOption = getXpathExplanationForToString();
Optional propsOption =
(elementProperties.size() == 1 && (!xpathOption.orElse("").contains(", ") || xpathOption.equals(describedBy))) ?
getPropertiesToStringForLength1() :
(elementProperties.size() == 2 && !xpathOption.orElse("").contains(" ")) ?
Optional.of(format("that %s, and %s",
elementProperties.get(0), elementProperties.get(elementProperties.size() - 1))) :
(elementProperties.size() > 1 || (xpathOption.orElse("").contains(" ") && !elementProperties.isEmpty())) ?
getPropertiesToStringForLengthLargerThan2() :
Optional.empty();
return (xpathExplanation.isPresent() && !underlyingOption.isPresent() && !propsOption.isPresent()) ?
xpathExplanation.get() :
Stream.of(underlyingOption, xpathOption, propsOption).filter(Optional::isPresent).map(Optional::get).
collect(Collectors.joining(", "));
}
}
private BasicPath createWithSimpleRelation(Path path, String relation) {
verifyRelationBetweenElements(path);
String myXpath = getXPath().get();
boolean isInside = insideXpath.isPresent();
String processedXpath = isInside ? format("*[ancestor::%s and self::%s]", insideXpath.get(), xpath.orElse("*")) : myXpath;
String newAlternateXpath = getAlternateXPath().get() + format("[%s::%s]", oppositeRelation(relation), path.getAlternateXPath().get());
boolean useAlternateXpath = hasHeirarchy(processedXpath);
String newXpath = useAlternateXpath ? newAlternateXpath : (path.getXPath().get() + "/" + relation + "::" + processedXpath);
return builder().
withUnderlyingOptional(underlying).
withXpath(newXpath).
withAlternateXpath(newAlternateXpath).
withXpathExplanation(toString() + ", " + relation + " of " + path.toString()).
build();
}
private String wrapIfNeeded(Path path) {
return (path.toString().trim().contains(" ")) ? "(" + path + ")" : path.toString();
}
private BasicPath createWithHumanReadableRelation(Path path, String xpathRelation, String humanReadableRelation) {
verifyRelationBetweenElements(path);
String myXpath = getXPath().get();
boolean isInside = insideXpath.isPresent();
String processedXpath = isInside ? format("%s[ancestor::%s]", getXPathWithoutInsideClause().get(), insideXpath.get()) : myXpath;
String newAlternateXpath = getAlternateXPath().get() + format("[%s::%s]", oppositeRelation(xpathRelation), path.getAlternateXPath().get());
boolean useAlternateXpath = hasHeirarchy(processedXpath);
String newXpath = useAlternateXpath ? newAlternateXpath : (path.getXPath().get() + "/" + xpathRelation + "::" + processedXpath);
return builder().
withUnderlyingOptional(underlying).
withXpath(newXpath).
withAlternateXpath(newAlternateXpath).
withXpathExplanation(toString() + ", " + humanReadableRelation + " " + wrapIfNeeded(path)).
build();
}
private BasicPath createNewWithAdditionalProperty(ElementProperty prop) {
if (describedBy.isPresent()) {
return builder().withUnderlyingOptional(underlying).
withXpathOptional(getXPath()).
withAlternateXpathOptional(getAlternateXPath()).
withInsideXpathOptional(insideXpath).
withElementProperties(ImmutableList.of(prop)).
withXpathExplanation(describedBy.get()).build();
} else {
ImmutableList newProps = ImmutableList.builder().
addAll(elementProperties).add(prop).
build();
return builder().withUnderlyingOptional(underlying).
withXpathOptional(xpath).
withInsideXpathOptional(insideXpath).
withAlternateXpathOptional(getAlternateXPath()).
withElementProperties(newProps).
withDescribedByOptional(describedBy).
withXpathExplanationOptional(xpathExplanation).build();
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy