-
Notifications
You must be signed in to change notification settings - Fork 4
Feature/13 element finder #28
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
Changes from all commits
70c5cdf
a3d6cb1
d47c94a
b95e87e
3ca9faf
6221008
ecb9c0a
22f0ff4
2e0080a
b503d61
7c4c470
cba8d49
9350294
7c948cf
81383dd
2cde734
d8d96cd
b1ef703
4d9b80e
033abb7
ffa1c1b
0078bf7
913cc7a
4aaeb26
e667c18
64b9ae4
b360791
d872075
29e444a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
package aquality.selenium.core.elements; | ||
|
||
import org.openqa.selenium.WebElement; | ||
|
||
import java.util.function.Predicate; | ||
|
||
/** | ||
* Defines desired state for element with ability to handle exceptions. | ||
*/ | ||
public class DesiredState { | ||
|
||
private final Predicate<WebElement> desiredStatePredicate; | ||
private final String stateName; | ||
mialeska marked this conversation as resolved.
Show resolved
Hide resolved
|
||
private boolean isCatchingTimeoutException; | ||
private boolean isThrowingNoSuchElementException; | ||
|
||
public DesiredState(Predicate<WebElement> desiredStatePredicate, String stateName){ | ||
this.desiredStatePredicate = desiredStatePredicate; | ||
this.stateName = stateName; | ||
} | ||
|
||
public Predicate<WebElement> getElementStateCondition() { | ||
return desiredStatePredicate; | ||
} | ||
|
||
public String getStateName() { | ||
return stateName; | ||
} | ||
|
||
public DesiredState withCatchingTimeoutException(){ | ||
this.isCatchingTimeoutException = true; | ||
return this; | ||
} | ||
|
||
public DesiredState withThrowingNoSuchElementException(){ | ||
this.isThrowingNoSuchElementException = true; | ||
return this; | ||
} | ||
|
||
public boolean isCatchingInTimeoutException() { | ||
return isCatchingTimeoutException; | ||
} | ||
|
||
public boolean isThrowingNoSuchElementException() { | ||
return isThrowingNoSuchElementException; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
package aquality.selenium.core.elements; | ||
|
||
import aquality.selenium.core.elements.interfaces.IElementFinder; | ||
import aquality.selenium.core.localization.ILocalizedLogger; | ||
import aquality.selenium.core.waitings.IConditionalWait; | ||
import com.google.inject.Inject; | ||
import org.openqa.selenium.*; | ||
|
||
import java.util.ArrayList; | ||
import java.util.List; | ||
import java.util.concurrent.atomic.AtomicBoolean; | ||
|
||
/** | ||
* Implementation of IElementFinder. | ||
*/ | ||
public class ElementFinder implements IElementFinder { | ||
private final ILocalizedLogger localizedLogger; | ||
private final IConditionalWait conditionalWait; | ||
|
||
@Inject | ||
public ElementFinder(ILocalizedLogger localizedLogger, IConditionalWait conditionalWait) { | ||
this.localizedLogger = localizedLogger; | ||
this.conditionalWait = conditionalWait; | ||
} | ||
|
||
@Override | ||
public List<WebElement> findElements(By locator, DesiredState desiredState, Long timeoutInSeconds) { | ||
AtomicBoolean wasAnyElementFound = new AtomicBoolean(false); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why do we need this variable? why can't we operate only with 'resultElements.isEmpty()'? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. result elements is a different array, in fact it has elements after filtering, while this variable defines whether any element was there before filtering. please recheck your suggestion |
||
List<WebElement> resultElements = new ArrayList<>(); | ||
try { | ||
conditionalWait.waitFor(driver -> | ||
tryToFindElements(locator, desiredState, wasAnyElementFound, resultElements, driver), | ||
timeoutInSeconds, | ||
null); | ||
} catch (TimeoutException e) { | ||
handleTimeoutException(e, locator, desiredState, wasAnyElementFound.get()); | ||
} | ||
|
||
return resultElements; | ||
} | ||
|
||
protected boolean tryToFindElements(By locator, DesiredState desiredState, AtomicBoolean wasAnyElementFound, | ||
List<WebElement> resultElements, SearchContext context) { | ||
List<WebElement> currentFoundElements = context.findElements(locator); | ||
wasAnyElementFound.set(!currentFoundElements.isEmpty()); | ||
currentFoundElements | ||
.stream() | ||
.filter(desiredState.getElementStateCondition()) | ||
.forEachOrdered(resultElements::add); | ||
return !resultElements.isEmpty(); | ||
} | ||
|
||
/** | ||
* depends on configuration of DesiredState object it can be required to throw or not NoSuchElementException | ||
* | ||
* @param exception TimeoutException to handle | ||
* @param locator locator that is using to find elements | ||
* @param desiredState DesiredState object | ||
* @param wasAnyElementFound was any element found by locator or not. | ||
*/ | ||
protected void handleTimeoutException(TimeoutException exception, By locator, DesiredState desiredState, boolean wasAnyElementFound) { | ||
String message = String.format("No elements with locator '%1$s' were found in %2$s state", locator, desiredState.getStateName()); | ||
if (desiredState.isCatchingInTimeoutException()) { | ||
if (!wasAnyElementFound) { | ||
if (desiredState.isThrowingNoSuchElementException()) { | ||
throw new NoSuchElementException(message); | ||
} | ||
localizedLogger.debug("loc.no.elements.found.in.state", locator, desiredState.getStateName()); | ||
} else { | ||
localizedLogger.debug("loc.elements.were.found.but.not.in.state", locator, desiredState.getStateName()); | ||
} | ||
} else { | ||
String combinedMessage = String.format("%1$s: %2$s", exception.getMessage(), message); | ||
if (desiredState.isThrowingNoSuchElementException() && !wasAnyElementFound) { | ||
throw new NoSuchElementException(combinedMessage); | ||
} | ||
throw new TimeoutException(combinedMessage); | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
package aquality.selenium.core.elements; | ||
|
||
public enum ElementState { | ||
DISPLAYED("displayed"), | ||
EXISTS_IN_ANY_STATE("exists"); | ||
|
||
private final String state; | ||
|
||
ElementState(String state) { | ||
this.state = state; | ||
} | ||
|
||
@Override | ||
public String toString() { | ||
return state; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
package aquality.selenium.core.elements; | ||
|
||
import aquality.selenium.core.elements.interfaces.IElementFinder; | ||
|
||
/** | ||
* Describes implementations of elements services to be registered in DI container. | ||
*/ | ||
public interface IElementsModule { | ||
/** | ||
* @return class which implements IElementFinder | ||
*/ | ||
default Class<? extends IElementFinder> getElementFinderImplementation() { | ||
return ElementFinder.class; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
package aquality.selenium.core.elements; | ||
|
||
import aquality.selenium.core.localization.ILocalizedLogger; | ||
import aquality.selenium.core.waitings.IConditionalWait; | ||
import org.openqa.selenium.By; | ||
import org.openqa.selenium.SearchContext; | ||
import org.openqa.selenium.WebElement; | ||
|
||
import java.util.ArrayList; | ||
import java.util.List; | ||
import java.util.concurrent.TimeoutException; | ||
import java.util.concurrent.atomic.AtomicBoolean; | ||
import java.util.function.Supplier; | ||
|
||
/** | ||
* Implementation of IElementFinder for a relative SearchContext. | ||
*/ | ||
public class RelativeElementFinder extends ElementFinder { | ||
private final IConditionalWait conditionalWait; | ||
private final Supplier<SearchContext> searchContextSupplier; | ||
|
||
public RelativeElementFinder(ILocalizedLogger localizedLogger, IConditionalWait conditionalWait, | ||
Supplier<SearchContext> searchContextSupplier) { | ||
super(localizedLogger, conditionalWait); | ||
this.conditionalWait = conditionalWait; | ||
this.searchContextSupplier = searchContextSupplier; | ||
} | ||
|
||
@Override | ||
public List<WebElement> findElements(By locator, DesiredState desiredState, Long timeoutInSeconds) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. if I am right the difference between this method and another one from the ElementFinder class that here we use 'searchContextSupplier.get()' instead 'driver' - can we create one parametrized and re-use it for both cases by passing necessary supplier? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. actually no, you are wrong. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. refactored, please check again |
||
AtomicBoolean wasAnyElementFound = new AtomicBoolean(false); | ||
List<WebElement> resultElements = new ArrayList<>(); | ||
try { | ||
conditionalWait.waitForTrue(() -> | ||
tryToFindElements(locator, desiredState, wasAnyElementFound, resultElements, | ||
searchContextSupplier.get()), | ||
timeoutInSeconds, | ||
null); | ||
} catch (TimeoutException e) { | ||
handleTimeoutException(new org.openqa.selenium.TimeoutException(e.getMessage(), e), locator, desiredState, | ||
wasAnyElementFound.get()); | ||
} catch (org.openqa.selenium.TimeoutException e) { | ||
handleTimeoutException(e, locator, desiredState, wasAnyElementFound.get()); | ||
} | ||
|
||
return resultElements; | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can we make this class package-private? and do not we need to extract an interface of it?
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
no, we cannot make it private, we will need it in child libraries.
no, I don't think that we need to extract an interface of it. This class is a simple model, and we expect it to remain the same. We don't register it in DI, so the interface is needless