Skip to content

Selenium 4 relative locators #316

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
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
@@ -0,0 +1,92 @@
package ru.tinkoff.qa.neptune.selenium.functions.searching;

import org.openqa.selenium.SearchContext;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.WrapsElement;
import ru.tinkoff.qa.neptune.core.api.steps.annotations.StepParameter;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;

public abstract class FindElementsBuilder<R, S extends Iterable<R>> implements Function<SearchContext, S> {
@StepParameter("Above")
private List<Object> above = new ArrayList<>();
@StepParameter("Below")
private List<Object> below = new ArrayList<>();
@StepParameter("Left of")
private List<Object> toLeftOf = new ArrayList<>();
@StepParameter("Right of")
private List<Object> toRightOf = new ArrayList<>();
@StepParameter("Near")
private List<Object> near = new ArrayList<>();
@StepParameter("Near with distance")
private Map<Object, Integer> nearWithDistance = new HashMap<>();

abstract void buildLocator(SearchContext searchContext);

WebElement performFind(SearchContext searchContext, SearchSupplier<?> find) {
var found = find.get().apply(searchContext);
if (found instanceof WrapsElement) {
return ((WrapsElement) found).getWrappedElement();
}
return (WebElement) found;
}

public FindElementsBuilder<R, S> above(Object above) {
this.above.add(above);
return this;
}

public FindElementsBuilder<R, S> below(Object below) {
this.below.add(below);
return this;
}

public FindElementsBuilder<R, S> toLeftOf(Object toLeftOf) {
this.toLeftOf.add(toLeftOf);
return this;
}

public FindElementsBuilder<R, S> toRightOf(Object toRightOf) {
this.toRightOf.add(toRightOf);
return this;
}

public FindElementsBuilder<R, S> near(Object near) {
this.near.add(near);
return this;
}

public FindElementsBuilder<R, S> near(Object near, Integer distance) {
this.nearWithDistance.put(near, distance);
return this;
}


public List<Object> getAbove() {
return above;
}

public List<Object> getBelow() {
return below;
}

public List<Object> getToLeftOf() {
return toLeftOf;
}

public List<Object> getToRightOf() {
return toRightOf;
}

public List<Object> getNear() {
return near;
}

public Map<Object, Integer> getNearWithDistance() {
return nearWithDistance;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,14 @@
import static com.google.common.base.Preconditions.checkArgument;
import static java.util.Objects.nonNull;
import static java.util.stream.Collectors.toList;
import static org.openqa.selenium.support.locators.RelativeLocator.with;
import static ru.tinkoff.qa.neptune.selenium.functions.searching.CGLibProxyBuilder.createProxy;
import static ru.tinkoff.qa.neptune.selenium.functions.searching.ToStringFormer.getMultipleToString;

final class FindWebElements implements Function<SearchContext, List<WebElement>> {

private final By by;
final class FindWebElements extends FindElementsBuilder<WebElement, List<WebElement>> {
private By by;
private By fullBy;
private boolean isBuildStarted;

private FindWebElements(By by) {
checkArgument(nonNull(by), "Locator by-strategy should be defined.");
Expand All @@ -27,11 +29,101 @@ static FindWebElements webElements(By by) {
return new FindWebElements(by);
}


@Override
protected void buildLocator(SearchContext searchContext) {
isBuildStarted = true;
var relativeLocator = with(by);

var aboveList = getAbove();
var belowList = getBelow();
var toLeftOfList = getToLeftOf();
var toRightOfList = getToRightOf();
var nearList = getNear();
var nearWithDistance = getNearWithDistance();

if (!aboveList.isEmpty()) {
for (var element : aboveList) {
if (element instanceof By) {
relativeLocator = relativeLocator.above((By) element);
} else if (element instanceof WebElement) {
relativeLocator = relativeLocator.above((WebElement) element);
} else if (element instanceof SearchSupplier<?>) {
relativeLocator = relativeLocator.above(performFind(searchContext, (SearchSupplier<?>) element));
}
}
}

if (!belowList.isEmpty()) {
for (var element : belowList) {
if (element instanceof By) {
relativeLocator = relativeLocator.below((By) element);
} else if (element instanceof WebElement) {
relativeLocator = relativeLocator.below((WebElement) element);
} else if (element instanceof SearchSupplier) {
relativeLocator = relativeLocator.below(performFind(searchContext, (SearchSupplier<?>) element));
}
}
}

if (!toLeftOfList.isEmpty()) {
for (var element : toLeftOfList) {
if (element instanceof By) {
relativeLocator = relativeLocator.toLeftOf((By) element);
} else if (element instanceof WebElement) {
relativeLocator = relativeLocator.toLeftOf((WebElement) element);
} else if (element instanceof SearchSupplier) {
relativeLocator = relativeLocator.toLeftOf(performFind(searchContext, (SearchSupplier<?>) element));
}
}
}

if (!toRightOfList.isEmpty()) {
for (var element : toRightOfList) {
if (element instanceof By) {
relativeLocator = relativeLocator.toRightOf((By) element);
} else if (element instanceof WebElement) {
relativeLocator = relativeLocator.toRightOf((WebElement) element);
} else if (element instanceof SearchSupplier) {
relativeLocator = relativeLocator.toRightOf(performFind(searchContext, (SearchSupplier<?>) element));
}
}
}

if (!nearList.isEmpty()) {
for (var element : nearList) {
if (element instanceof By) {
relativeLocator = relativeLocator.near((By) element);
} else if (element instanceof WebElement) {
relativeLocator = relativeLocator.near((WebElement) element);
} else if (element instanceof SearchSupplier) {
relativeLocator = relativeLocator.near(performFind(searchContext, (SearchSupplier<?>) element));
}
}
}

if (!nearWithDistance.isEmpty()) {
for (var element : nearWithDistance.entrySet()) {
if (element.getKey() instanceof By) {
relativeLocator = relativeLocator.near((By) element.getKey(), element.getValue());
} else if (element.getKey() instanceof WebElement) {
relativeLocator = relativeLocator.near((WebElement) element.getKey(), element.getValue());
} else if (element.getKey() instanceof SearchSupplier) {
relativeLocator = relativeLocator.near(performFind(searchContext, (SearchSupplier<?>) element.getKey()), element.getValue());
}
}
}

fullBy = relativeLocator;
}

@Override
public List<WebElement> apply(SearchContext searchContext) {
return new ArrayList<>(searchContext.findElements(by)
.stream().map(webElement -> createProxy(webElement.getClass(), new WebElementInterceptor(webElement, by)))
.collect(toList())) {
if (!isBuildStarted) {
buildLocator(searchContext);
}
return new ArrayList<>(searchContext.findElements(fullBy).stream()
.map(webElement -> createProxy(webElement.getClass(), new WebElementInterceptor(webElement, fullBy))).collect(toList())) {

public String toString() {
if (size() == 0) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Function;
import java.util.function.Predicate;

import static com.google.common.base.Preconditions.checkArgument;
Expand All @@ -21,7 +20,7 @@
import static ru.tinkoff.qa.neptune.selenium.functions.searching.ToStringFormer.getMultipleToString;
import static ru.tinkoff.qa.neptune.selenium.functions.searching.WidgetPriorityComparator.widgetPriorityComparator;

class FindWidgets<R extends Widget> implements Function<SearchContext, List<R>> {
class FindWidgets<R extends Widget> extends FindElementsBuilder<R, List<R>> {

private static final FindByBuilder BUILDER = new FindByBuilder();
private static final List<Class<? extends Widget>> SCAN_RESULT = new ArrayList<>(new ClassGraph()
Expand Down Expand Up @@ -95,7 +94,7 @@ public String toString() {
return getMultipleToString(this);
}
};

//TODO информация о классах и их By должна инициализироваться до момента выполнения функции
classesToInstantiate.forEach(clazz -> {
var by = BUILDER.buildIt(clazz);
result.addAll(searchContext.findElements(by).stream()
Expand All @@ -104,4 +103,8 @@ public String toString() {
});
return result;
}

@Override
protected void buildLocator(SearchContext searchContext) {
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@

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

import static ru.tinkoff.qa.neptune.core.api.steps.Criteria.OR;
import static ru.tinkoff.qa.neptune.selenium.functions.searching.CommonElementCriteria.*;
Expand All @@ -37,8 +36,11 @@
public final class MultipleSearchSupplier<R extends SearchContext> extends
SequentialGetStepSupplier.GetListChainedStepSupplier<Object, List<R>, SearchContext, R, MultipleSearchSupplier<R>> {

private MultipleSearchSupplier(Function<SearchContext, List<R>> originalFunction) {
private final FindElementsBuilder<?, ?> findBuilder;

private MultipleSearchSupplier(FindElementsBuilder<R, List<R>> originalFunction) {
super(originalFunction);
findBuilder = originalFunction;
from(new SearchingInitialFunction());
timeOut(ELEMENT_WAITING_DURATION.get());
addIgnored(StaleElementReferenceException.class);
Expand Down Expand Up @@ -686,6 +688,36 @@ public MultipleSearchSupplier<R> timeOut(Duration timeOut) {
return super.timeOut(timeOut);
}

public MultipleSearchSupplier<R> above(Object by) {
findBuilder.above(by);
return this;
}

public MultipleSearchSupplier<R> below(Object by) {
findBuilder.below(by);
return this;
}

public MultipleSearchSupplier<R> toLeftOf(Object by) {
findBuilder.toLeftOf(by);
return this;
}

public MultipleSearchSupplier<R> toRightOf(Object by) {
findBuilder.toRightOf(by);
return this;
}

public MultipleSearchSupplier<R> near(Object by) {
findBuilder.near(by);
return this;
}

public MultipleSearchSupplier<R> near(Object by, Integer distance) {
findBuilder.near(by, distance);
return this;
}

@Override
public MultipleSearchSupplier<R> clone() {
return super.clone();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
import ru.tinkoff.qa.neptune.selenium.captors.WebElementImageCaptor;

import java.time.Duration;
import java.util.function.Function;

import static ru.tinkoff.qa.neptune.core.api.steps.Criteria.OR;
import static ru.tinkoff.qa.neptune.selenium.functions.searching.CommonElementCriteria.*;
Expand All @@ -36,8 +35,11 @@
public final class SearchSupplier<R extends SearchContext>
extends SequentialGetStepSupplier.GetObjectFromIterableChainedStepSupplier<Object, R, SearchContext, SearchSupplier<R>> {

private <S extends Iterable<R>> SearchSupplier(Function<SearchContext, S> originalFunction) {
private final FindElementsBuilder<?, ?> findBuilder;

private <S extends Iterable<R>> SearchSupplier(FindElementsBuilder<R, S> originalFunction) {
super(originalFunction);
findBuilder = originalFunction;
from(new SearchingInitialFunction());
timeOut(ELEMENT_WAITING_DURATION.get());
addIgnored(StaleElementReferenceException.class);
Expand Down Expand Up @@ -681,6 +683,36 @@ public SearchSupplier<R> timeOut(Duration timeOut) {
return super.timeOut(timeOut);
}

public SearchSupplier<R> above(Object by) {
findBuilder.above(by);
return this;
}

public SearchSupplier<R> below(Object by) {
findBuilder.below(by);
return this;
}

public SearchSupplier<R> toLeftOf(Object by) {
findBuilder.toLeftOf(by);
return this;
}

public SearchSupplier<R> toRightOf(Object by) {
findBuilder.toRightOf(by);
return this;
}

public SearchSupplier<R> near(Object by) {
findBuilder.near(by);
return this;
}

public SearchSupplier<R> near(Object by, Integer distance) {
findBuilder.near(by, distance);
return this;
}

@Override
public SearchSupplier<R> clone() {
return super.clone();
Expand Down