Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor collection conditions #2312

Merged
merged 25 commits into from
May 28, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
4906e4c
Introduce method `CollectionCondition.check(CollectionSource)`
asolntsev May 25, 2023
a8c2450
refactor $$.anyMatch to use new check method
asolntsev May 25, 2023
41d705e
improve PredicateCollectionCondition.toString()
asolntsev May 25, 2023
65ac21f
refactor $$.allMatch to use new check method
asolntsev May 25, 2023
f9197d8
refactor $$.noneMatch to use new check method
asolntsev May 25, 2023
edc47a0
start using new method `fail(..CheckResult..)`
asolntsev May 25, 2023
3b45760
refactor $$.attributes to use new method `fail(CheckResult)`
asolntsev May 25, 2023
87a75f1
refactor $$.itemWithText to use new `check` method
asolntsev May 26, 2023
45fa46f
refactor CustomCollectionConditionTest to use new methods `check` and…
asolntsev May 26, 2023
5a60784
move all collections-related tests to package "integration.collections"
asolntsev May 26, 2023
343f472
refactor tests for $$.sizeNotEqual
asolntsev May 26, 2023
7b5d7a6
refactor $$.sizeNotEqual to use new `check` method
asolntsev May 26, 2023
294b50e
refactor tests for $$.sizeGreaterThan
asolntsev May 26, 2023
a0e9d97
refactor tests for $$.sizeGreaterThanOrEqual
asolntsev May 27, 2023
6bdbd50
refactor tests for $$.sizeLessThan
asolntsev May 27, 2023
c57814a
refactor tests for $$.sizeLessThanOrEqual
asolntsev May 27, 2023
a5e931f
refactor tests for $$.sizeGreaterThanOrEqual
asolntsev May 27, 2023
0bef8ae
refactor tests for $$.exactTexts
asolntsev May 27, 2023
ceb0a3e
refactor tests for $$.containExactTextsCaseSensitive
asolntsev May 27, 2023
535e34d
refactor tests for $$.exactTextsCaseSensitiveInAnyOrder
asolntsev May 28, 2023
51b9e07
move tests for $$.attributes for a dedicated test-class
asolntsev May 28, 2023
8cdd60a
speed up the slowest collections tests
asolntsev May 28, 2023
1f2a865
move tests for $$.textsInAnyOrder for a dedicated test-class
asolntsev May 28, 2023
e620f3d
refactor tests for $$.exactTextsCaseSensitive
asolntsev May 28, 2023
ea491b5
refactor tests for $$.texts
asolntsev May 28, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
8 changes: 8 additions & 0 deletions src/main/java/com/codeborne/selenide/CheckResult.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.github.bsideup.jabel.Desugar;

import javax.annotation.CheckReturnValue;
import javax.annotation.Nullable;
import javax.annotation.ParametersAreNonnullByDefault;
import java.time.LocalDateTime;
Expand Down Expand Up @@ -39,4 +40,11 @@ public enum Verdict {
public String toString() {
return String.format("%s @ %s%n", actualValue, timeFormat.format(timestamp));
}

@Nullable
@CheckReturnValue
@SuppressWarnings("unchecked")
public <T> T getActualValue() {
return (T) actualValue();
}
}
61 changes: 52 additions & 9 deletions src/main/java/com/codeborne/selenide/CollectionCondition.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import com.codeborne.selenide.collections.Texts;
import com.codeborne.selenide.collections.TextsInAnyOrder;
import com.codeborne.selenide.impl.CollectionSource;
import com.github.bsideup.jabel.Desugar;
import org.openqa.selenium.WebElement;

import javax.annotation.CheckReturnValue;
Expand All @@ -35,25 +36,53 @@ public abstract class CollectionCondition {
protected String explanation;

/**
* @deprecated Please implement method {@link #check(Driver, List)} instead.
* @deprecated Please implement method {@link #check(CollectionSource)} instead.
*/
@Deprecated
@CheckReturnValue
public boolean test(List<WebElement> elements) {
throw new UnsupportedOperationException("Implement method 'check' (recommended) or 'test' (deprecated)");
throw new UnsupportedOperationException("Implement method 'check' (powerful) or 'test' (simple)");
}

@Nonnull
@CheckReturnValue
public CheckResult check(Driver driver, List<WebElement> elements) {
boolean result = test(elements);
return new CheckResult(result ? ACCEPT : REJECT, null);
return result ? new CheckResult(ACCEPT, null) : new CheckResult(REJECT, new ActualElementsHolder(elements));
}

public abstract void fail(CollectionSource collection,
@Nullable List<WebElement> elements,
@Nullable Exception cause,
long timeoutMs);
/**
* The most powerful way to implement condition.
* Can check the collection using JavaScript or any other effective means.
* Also, can return "actual values" in the returned {@link CheckResult} object.
*/
@Nonnull
@CheckReturnValue
public CheckResult check(CollectionSource collection) {
List<WebElement> elements = collection.getElements();
return check(collection.driver(), elements);
}

@Deprecated
public void fail(CollectionSource collection,
@Nullable List<WebElement> elements,
@Nullable Exception cause,
long timeoutMs) {
throw new UnsupportedOperationException(
"Implement method 'fail(..CheckResult..)' (powerful) or 'fail(..List<WebElement>..)' (simple)");
}

/**
* Every subclass should implement this method.
* We left here the default implementation only because of backward compatibility.
*/
public void fail(CollectionSource collection,
CheckResult lastCheckResult,
@Nullable Exception cause,
long timeoutMs) {
List<WebElement> actualElements = lastCheckResult.actualValue() instanceof ActualElementsHolder holder ? holder.elements() : null;
fail(collection, actualElements, cause, timeoutMs);
}

public static CollectionCondition empty = size(0);

Expand Down Expand Up @@ -347,14 +376,16 @@ public boolean missingElementSatisfiesCondition() {

@Override
@Deprecated
@SuppressWarnings("deprecation")
public boolean test(List<WebElement> input) {
return delegate.test(input);
}

@Nonnull
@CheckReturnValue
@Override
public CheckResult check(Driver driver, List<WebElement> elements) {
return delegate.check(driver, elements);
public CheckResult check(CollectionSource collection) {
return delegate.check(collection);
}
}

Expand All @@ -367,4 +398,16 @@ public CollectionCondition because(String explanation) {
}

public abstract boolean missingElementSatisfiesCondition();

/**
* A temporary solution for keeping backward compatibility
* until we throw away old method {@link #test(List)} and {@link #fail(CollectionSource, List, Exception, long)}
*/
@Desugar
private record ActualElementsHolder(List<WebElement> elements) {
@Override
public String toString() {
return "Elements: " + elements;
}
}
}
14 changes: 10 additions & 4 deletions src/main/java/com/codeborne/selenide/ElementsCollection.java
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
import java.util.stream.StreamSupport;

import static com.codeborne.selenide.CheckResult.Verdict.ACCEPT;
import static com.codeborne.selenide.CheckResult.Verdict.REJECT;
import static com.codeborne.selenide.Condition.exist;
import static com.codeborne.selenide.Condition.not;
import static com.codeborne.selenide.logevents.ErrorsCollector.validateAssertionMode;
Expand Down Expand Up @@ -160,12 +161,12 @@ protected ElementsCollection should(String prefix, Duration timeout, CollectionC

protected void waitUntil(CollectionCondition condition, Duration timeout) {
Throwable lastError = null;
List<WebElement> actualElements = null;
CheckResult lastCheckResult = new CheckResult(REJECT, null);
Stopwatch stopwatch = new Stopwatch(timeout.toMillis());
do {
try {
actualElements = collection.getElements();
if (condition.check(collection.driver(), actualElements).verdict() == ACCEPT) {
lastCheckResult = condition.check(collection);
if (lastCheckResult.verdict() == ACCEPT) {
return;
}
}
Expand All @@ -192,7 +193,7 @@ else if (lastError instanceof UIAssertionError uiAssertionError) {
throw uiAssertionError;
}
else {
condition.fail(collection, actualElements, (Exception) lastError, timeout.toMillis());
condition.fail(collection, lastCheckResult, (Exception) lastError, timeout.toMillis());
}
}

Expand Down Expand Up @@ -328,9 +329,11 @@ private static String getText(WebElement element) {
/**
* Gets all the specific attribute values in elements collection
* @see <a href="https://github.com/selenide/selenide/wiki/do-not-use-getters-in-tests">NOT RECOMMENDED</a>
* @deprecated Instead of getting attributes, verify them with {@code $$.shouldHave(attributes(...));}.
*/
@CheckReturnValue
@Nonnull
@Deprecated
public List<String> attributes(String attribute) {
return attributes(attribute, getElements());
}
Expand All @@ -340,13 +343,16 @@ public List<String> attributes(String attribute) {
*
* @param elements Any collection of WebElements
* @return Texts (or exceptions in case of any WebDriverExceptions)
* @deprecated Instead of getting attributes, verify them with {@code $$.shouldHave(attributes(...));}.
*/
@CheckReturnValue
@Nonnull
@Deprecated
public static List<String> attributes(String attribute, Collection<WebElement> elements) {
return elements.stream().map(e -> getAttribute(e, attribute)).collect(toList());
}

@Deprecated
private static String getAttribute(WebElement element, String attribute) {
try {
return element.getAttribute(attribute);
Expand Down
16 changes: 11 additions & 5 deletions src/main/java/com/codeborne/selenide/collections/AllMatch.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
package com.codeborne.selenide.collections;

import com.codeborne.selenide.CheckResult;
import com.codeborne.selenide.Driver;
import org.openqa.selenium.WebElement;

import javax.annotation.CheckReturnValue;
import javax.annotation.Nonnull;
import javax.annotation.ParametersAreNonnullByDefault;
import java.util.List;
import java.util.function.Predicate;
Expand All @@ -12,11 +16,13 @@ public AllMatch(String description, Predicate<WebElement> predicate) {
super("all", description, predicate);
}

@Nonnull
@CheckReturnValue
@Override
public boolean test(List<WebElement> elements) {
if (elements.isEmpty()) {
return false;
}
return elements.stream().allMatch(predicate);
public CheckResult check(Driver driver, List<WebElement> elements) {
return new CheckResult(
!elements.isEmpty() && elements.stream().allMatch(predicate),
elements
);
}
}
12 changes: 9 additions & 3 deletions src/main/java/com/codeborne/selenide/collections/AnyMatch.java
Original file line number Diff line number Diff line change
@@ -1,19 +1,25 @@
package com.codeborne.selenide.collections;

import com.codeborne.selenide.CheckResult;
import com.codeborne.selenide.Driver;
import org.openqa.selenium.WebElement;

import javax.annotation.CheckReturnValue;
import javax.annotation.Nonnull;
import javax.annotation.ParametersAreNonnullByDefault;
import java.util.List;
import java.util.function.Predicate;

@ParametersAreNonnullByDefault
public class AnyMatch extends PredicateCollectionCondition {
public AnyMatch(String description, Predicate<WebElement> predicate) {
super("any", description, predicate);
super("any of", description, predicate);
}

@Nonnull
@CheckReturnValue
@Override
public boolean test(List<WebElement> elements) {
return elements.stream().anyMatch(predicate);
public CheckResult check(Driver driver, List<WebElement> elements) {
return new CheckResult(elements.stream().anyMatch(predicate), elements);
}
}
29 changes: 18 additions & 11 deletions src/main/java/com/codeborne/selenide/collections/Attributes.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import com.codeborne.selenide.CheckResult;
import com.codeborne.selenide.CollectionCondition;
import com.codeborne.selenide.Driver;
import com.codeborne.selenide.ElementsCollection;
import com.codeborne.selenide.ex.AttributesMismatch;
import com.codeborne.selenide.ex.AttributesSizeMismatch;
import com.codeborne.selenide.ex.ElementNotFound;
Expand All @@ -14,12 +13,14 @@
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.annotation.ParametersAreNonnullByDefault;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

import static com.codeborne.selenide.CheckResult.Verdict.ACCEPT;
import static com.codeborne.selenide.CheckResult.Verdict.REJECT;
import static java.util.Collections.unmodifiableList;
import static java.util.Objects.requireNonNull;

@ParametersAreNonnullByDefault
public class Attributes extends CollectionCondition {
Expand All @@ -42,31 +43,37 @@ public CheckResult check(Driver driver, List<WebElement> elements) {
return new CheckResult(REJECT, elements.size());
}

List<String> actualAttributeValues = new ArrayList<>(expectedValues.size());
for (int i = 0; i < expectedValues.size(); i++) {
WebElement element = elements.get(i);
String expectedValue = expectedValues.get(i);
String actualAttributeValue = element.getAttribute(attribute);
actualAttributeValues.add(actualAttributeValue);

if (!Objects.equals(actualAttributeValue, expectedValue)) {
return new CheckResult(REJECT, actualAttributeValue);
return new CheckResult(REJECT, actualAttributeValues);
}
}
return new CheckResult(ACCEPT, null);
}

@Override
public void fail(CollectionSource collection,
@Nullable List<WebElement> elements,
CheckResult lastCheckResult,
@Nullable Exception cause,
long timeoutMs) {
if (elements == null || elements.isEmpty()) {
throw new ElementNotFound(collection, toString(), timeoutMs, cause);
} else if (elements.size() != expectedValues.size()) {
throw new AttributesSizeMismatch(collection.driver(), attribute, collection, expectedValues,
ElementsCollection.attributes(attribute, elements), explanation, timeoutMs, cause);
} else {
throw new AttributesMismatch(collection.driver(), attribute, collection, expectedValues,
ElementsCollection.attributes(attribute, elements), explanation, timeoutMs, cause);
if (lastCheckResult.actualValue() instanceof Integer actualSize) {
if (actualSize == 0) {
throw new ElementNotFound(collection, toString(), timeoutMs, cause);
}
else {
throw new AttributesSizeMismatch(collection.driver(), attribute, collection, expectedValues,
actualSize, explanation, timeoutMs, cause);
}
}

throw new AttributesMismatch(collection.driver(), attribute, collection, expectedValues,
requireNonNull(lastCheckResult.getActualValue()), explanation, timeoutMs, cause);
}

@Override
Expand Down
16 changes: 10 additions & 6 deletions src/main/java/com/codeborne/selenide/collections/ItemWithText.java
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
package com.codeborne.selenide.collections;

import com.codeborne.selenide.CheckResult;
import com.codeborne.selenide.CollectionCondition;
import com.codeborne.selenide.ElementsCollection;
import com.codeborne.selenide.ex.ElementWithTextNotFound;
import com.codeborne.selenide.impl.CollectionSource;
import org.openqa.selenium.WebElement;

import javax.annotation.CheckReturnValue;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.annotation.ParametersAreNonnullByDefault;
import java.util.List;
Expand All @@ -22,21 +24,23 @@ public ItemWithText(String expectedText) {
this.expectedText = expectedText;
}

@Nonnull
@CheckReturnValue
@Override
public boolean test(List<WebElement> elements) {
return ElementsCollection
.texts(elements)
.contains(expectedText);
public CheckResult check(CollectionSource collection) {
List<WebElement> elements = collection.getElements();
List<String> texts = ElementsCollection.texts(elements);

return new CheckResult(texts.contains(expectedText), texts);
}

@Override
public void fail(CollectionSource collection,
@Nullable List<WebElement> elements,
CheckResult lastCheckResult,
@Nullable Exception cause,
long timeoutMs) {
throw new ElementWithTextNotFound(
collection, singletonList(expectedText), ElementsCollection.texts(elements), explanation, timeoutMs, cause);
collection, singletonList(expectedText), lastCheckResult.getActualValue(), explanation, timeoutMs, cause);
}

@CheckReturnValue
Expand Down
18 changes: 12 additions & 6 deletions src/main/java/com/codeborne/selenide/collections/NoneMatch.java
Original file line number Diff line number Diff line change
@@ -1,22 +1,28 @@
package com.codeborne.selenide.collections;

import com.codeborne.selenide.CheckResult;
import com.codeborne.selenide.Driver;
import org.openqa.selenium.WebElement;

import javax.annotation.CheckReturnValue;
import javax.annotation.Nonnull;
import javax.annotation.ParametersAreNonnullByDefault;
import java.util.List;
import java.util.function.Predicate;

@ParametersAreNonnullByDefault
public class NoneMatch extends PredicateCollectionCondition {
public NoneMatch(String description, Predicate<WebElement> predicate) {
super("none", description, predicate);
super("none of", description, predicate);
}

@Nonnull
@CheckReturnValue
@Override
public boolean test(List<WebElement> elements) {
if (elements.isEmpty()) {
return false;
}
return elements.stream().noneMatch(predicate);
public CheckResult check(Driver driver, List<WebElement> elements) {
return new CheckResult(
!elements.isEmpty() && elements.stream().noneMatch(predicate),
elements
);
}
}