Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,11 @@
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.ui.FluentWait;

import java.lang.ref.WeakReference;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.function.Function;
import java.util.function.Supplier;

Expand All @@ -46,10 +48,36 @@ class AppiumElementLocator implements CacheableLocator {
private final boolean shouldCache;
private final By by;
private final Duration duration;
private final WeakReference<SearchContext> searchContextReference;
private final SearchContext searchContext;

private WebElement cachedElement;
private List<WebElement> cachedElementList;

/**
* Creates a new mobile element locator. It instantiates {@link WebElement}
* using @AndroidFindBy (-s), @iOSFindBy (-s) and @FindBy (-s) annotation
* sets
*
* @param searchContextReference The context reference to use when finding the element
* @param by a By locator strategy
* @param shouldCache is the flag that signalizes that elements which
* are found once should be cached
* @param duration timeout parameter for the element to be found
*/
AppiumElementLocator(
WeakReference<SearchContext> searchContextReference,
By by,
boolean shouldCache,
Duration duration
) {
this.searchContextReference = searchContextReference;
this.searchContext = null;
this.shouldCache = shouldCache;
this.duration = duration;
this.by = by;
}

/**
* Creates a new mobile element locator. It instantiates {@link WebElement}
* using @AndroidFindBy (-s), @iOSFindBy (-s) and @FindBy (-s) annotation
Expand All @@ -61,15 +89,25 @@ class AppiumElementLocator implements CacheableLocator {
* are found once should be cached
* @param duration timeout parameter for the element to be found
*/

public AppiumElementLocator(SearchContext searchContext, By by, boolean shouldCache,
Duration duration) {
public AppiumElementLocator(
SearchContext searchContext,
By by,
boolean shouldCache,
Duration duration
) {
this.searchContextReference = null;
this.searchContext = searchContext;
this.shouldCache = shouldCache;
this.duration = duration;
this.by = by;
}

private Optional<SearchContext> getSearchContext() {
return searchContext == null
? Optional.ofNullable(searchContextReference).map(WeakReference::get)
: Optional.of(searchContext);
}

/**
* This methods makes sets some settings of the {@link By} according to
* the given instance of {@link SearchContext}. If there is some {@link ContentMappedBy}
Expand All @@ -85,8 +123,7 @@ private static By getBy(By currentBy, SearchContext currentContent) {
return currentBy;
}

return ContentMappedBy.class.cast(currentBy)
.useContent(getCurrentContentType(currentContent));
return ((ContentMappedBy) currentBy).useContent(getCurrentContentType(currentContent));
}

private <T> T waitFor(Supplier<T> supplier) {
Expand All @@ -98,8 +135,7 @@ private <T> T waitFor(Supplier<T> supplier) {
return wait.until(function);
} catch (TimeoutException e) {
if (function.foundStaleElementReferenceException != null) {
throw StaleElementReferenceException
.class.cast(function.foundStaleElementReferenceException);
throw (StaleElementReferenceException) function.foundStaleElementReferenceException;
}
throw e;
}
Expand All @@ -113,10 +149,15 @@ public WebElement findElement() {
return cachedElement;
}

SearchContext searchContext = getSearchContext()
.orElseThrow(() -> new IllegalStateException(
String.format("The element %s is not locatable anymore "
+ "because its context has been garbage collected", by)
));

By bySearching = getBy(this.by, searchContext);
try {
WebElement result = waitFor(() ->
searchContext.findElement(bySearching));
WebElement result = waitFor(() -> searchContext.findElement(bySearching));
if (shouldCache) {
cachedElement = result;
}
Expand All @@ -134,12 +175,17 @@ public List<WebElement> findElements() {
return cachedElementList;
}

SearchContext searchContext = getSearchContext()
.orElseThrow(() -> new IllegalStateException(
String.format("Elements %s are not locatable anymore "
+ "because their context has been garbage collected", by)
));

List<WebElement> result;
try {
result = waitFor(() -> {
List<WebElement> list = searchContext
.findElements(getBy(by, searchContext));
return list.size() > 0 ? list : null;
List<WebElement> list = searchContext.findElements(getBy(by, searchContext));
return list.isEmpty() ? null : list;
});
} catch (TimeoutException | StaleElementReferenceException e) {
result = new ArrayList<>();
Expand Down Expand Up @@ -171,30 +217,22 @@ public T apply(Supplier<T> supplier) {
return supplier.get();
} catch (Throwable e) {
boolean isRootCauseStaleElementReferenceException = false;
Throwable shouldBeThrown;
boolean isRootCauseInvalidSelector = isInvalidSelectorRootCause(e);

if (!isRootCauseInvalidSelector) {
isRootCauseStaleElementReferenceException = isStaleElementReferenceException(e);
}

if (isRootCauseStaleElementReferenceException) {
foundStaleElementReferenceException = extractReadableException(e);
}
if (isRootCauseInvalidSelector || isRootCauseStaleElementReferenceException) {
return null;
}

if (!isRootCauseInvalidSelector & !isRootCauseStaleElementReferenceException) {
shouldBeThrown = extractReadableException(e);
if (shouldBeThrown != null) {
if (NoSuchElementException.class.equals(shouldBeThrown.getClass())) {
throw NoSuchElementException.class.cast(shouldBeThrown);
} else {
throw new WebDriverException(shouldBeThrown);
}
} else {
throw new WebDriverException(e);
}
Throwable excToThrow = extractReadableException(e);
if (excToThrow instanceof WebDriverException) {
throw (WebDriverException) excToThrow;
} else {
return null;
throw new WebDriverException(excToThrow);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import org.openqa.selenium.SearchContext;

import javax.annotation.Nullable;
import java.lang.ref.WeakReference;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Field;
import java.time.Duration;
Expand All @@ -32,29 +33,54 @@

public class AppiumElementLocatorFactory implements CacheableElementLocatorFactory {
private final SearchContext searchContext;
private final WeakReference<SearchContext> searchContextReference;
private final Duration duration;
private final AppiumByBuilder builder;

/**
* Creates a new mobile element locator factory.
*
* @param searchContext The context to use when finding the element
* @param duration timeout parameters for the elements to be found
* @param builder is handler of Appium-specific page object annotations
* @param duration timeout parameters for the elements to be found
* @param builder is handler of Appium-specific page object annotations
*/
public AppiumElementLocatorFactory(SearchContext searchContext, Duration duration,
AppiumByBuilder builder) {
public AppiumElementLocatorFactory(
SearchContext searchContext,
Duration duration,
AppiumByBuilder builder
) {
this.searchContext = searchContext;
this.searchContextReference = null;
this.duration = duration;
this.builder = builder;
}

public @Nullable CacheableLocator createLocator(Field field) {
return this.createLocator((AnnotatedElement) field);
/**
* Creates a new mobile element locator factory.
*
* @param searchContextReference The context reference to use when finding the element
* @param duration timeout parameters for the elements to be found
* @param builder is handler of Appium-specific page object annotations
*/
AppiumElementLocatorFactory(
WeakReference<SearchContext> searchContextReference,
Duration duration,
AppiumByBuilder builder
) {
this.searchContextReference = searchContextReference;
this.searchContext = null;
this.duration = duration;
this.builder = builder;
}

@Nullable
@Override
public CacheableLocator createLocator(Field field) {
return this.createLocator((AnnotatedElement) field);
}

@Nullable
@Override
public CacheableLocator createLocator(AnnotatedElement annotatedElement) {
Duration customDuration;
if (annotatedElement.isAnnotationPresent(WithTimeout.class)) {
Expand All @@ -63,14 +89,13 @@ public CacheableLocator createLocator(AnnotatedElement annotatedElement) {
} else {
customDuration = duration;
}

builder.setAnnotated(annotatedElement);
By byResult = builder.buildBy();

return ofNullable(byResult)
.map(by -> new AppiumElementLocator(searchContext, by, builder.isLookupCached(), customDuration))
.map(by -> searchContextReference != null
? new AppiumElementLocator(searchContextReference, by, builder.isLookupCached(), customDuration)
: new AppiumElementLocator(searchContext, by, builder.isLookupCached(), customDuration)
)
.orElse(null);
}


}
Loading