2828import org .openqa .selenium .By ;
2929import org .openqa .selenium .NoSuchElementException ;
3030import org .openqa .selenium .SearchContext ;
31+ import org .openqa .selenium .StaleElementReferenceException ;
3132import org .openqa .selenium .TimeoutException ;
3233import org .openqa .selenium .WebDriver ;
34+ import org .openqa .selenium .WebDriverException ;
3335import org .openqa .selenium .WebElement ;
3436import org .openqa .selenium .support .ui .FluentWait ;
3537
3638import java .util .ArrayList ;
3739import java .util .List ;
3840import java .util .concurrent .TimeUnit ;
41+ import java .util .function .Supplier ;
3942
4043class AppiumElementLocator implements CacheableLocator {
4144
@@ -45,9 +48,9 @@ class AppiumElementLocator implements CacheableLocator {
4548 private final TimeOutDuration originalTimeOutDuration ;
4649 private final WebDriver originalWebDriver ;
4750 private final SearchContext searchContext ;
48- private final WaitingFunction waitingFunction ;
4951 private WebElement cachedElement ;
5052 private List <WebElement > cachedElementList ;
53+ private final String exceptionMessageIfElementNotFound ;
5154 /**
5255 * Creates a new mobile element locator. It instantiates {@link WebElement}
5356 * using @AndroidFindBy (-s), @iOSFindBy (-s) and @FindBy (-s) annotation
@@ -71,26 +74,27 @@ public AppiumElementLocator(SearchContext searchContext, By by, boolean shouldCa
7174 this .originalTimeOutDuration = originalDuration ;
7275 this .by = by ;
7376 this .originalWebDriver = originalWebDriver ;
74- waitingFunction = new WaitingFunction ( this . searchContext );
77+ this . exceptionMessageIfElementNotFound = "Can't locate an element by this strategy: " + by . toString ( );
7578 }
7679
7780 private void changeImplicitlyWaitTimeOut (long newTimeOut , TimeUnit newTimeUnit ) {
7881 originalWebDriver .manage ().timeouts ().implicitlyWait (newTimeOut , newTimeUnit );
7982 }
8083
81- // This method waits for not empty element list using all defined by
82- private List <WebElement > waitFor () {
83- // When we use complex By strategies (like ChainedBy or ByAll)
84- // there are some problems (StaleElementReferenceException, implicitly
85- // wait time out
86- // for each chain By section, etc)
84+ private <T extends Object > T waitFor (Supplier <T > supplier ) {
85+ WaitingFunction <T > function = new WaitingFunction <>();
8786 try {
8887 changeImplicitlyWaitTimeOut (0 , TimeUnit .SECONDS );
89- FluentWait <By > wait = new FluentWait <>(by );
88+ FluentWait <Supplier <T >> wait = new FluentWait <>(supplier )
89+ .ignoring (NoSuchElementException .class );
9090 wait .withTimeout (timeOutDuration .getTime (), timeOutDuration .getTimeUnit ());
91- return wait .until (waitingFunction );
91+ return wait .until (function );
9292 } catch (TimeoutException e ) {
93- return new ArrayList <>();
93+ if (function .foundStaleElementReferenceException != null ) {
94+ throw StaleElementReferenceException
95+ .class .cast (function .foundStaleElementReferenceException );
96+ }
97+ throw e ;
9498 } finally {
9599 changeImplicitlyWaitTimeOut (originalTimeOutDuration .getTime (), originalTimeOutDuration .getTimeUnit ());
96100 }
@@ -103,19 +107,17 @@ public WebElement findElement() {
103107 if (cachedElement != null && shouldCache ) {
104108 return cachedElement ;
105109 }
106- List < WebElement > result = waitFor ();
107- if ( result . size () == 0 ) {
108- String message = "Can't locate an element by this strategy: " + by . toString ();
109- if ( waitingFunction . foundStaleElementReferenceException != null ) {
110- throw new NoSuchElementException ( message ,
111- waitingFunction . foundStaleElementReferenceException ) ;
110+
111+ try {
112+ WebElement result = waitFor (() ->
113+ searchContext . findElement ( by ));
114+ if ( shouldCache ) {
115+ cachedElement = result ;
112116 }
113- throw new NoSuchElementException (message );
114- }
115- if (shouldCache ) {
116- cachedElement = result .get (0 );
117+ return result ;
118+ } catch (TimeoutException | StaleElementReferenceException e ) {
119+ throw new NoSuchElementException (exceptionMessageIfElementNotFound , e );
117120 }
118- return result .get (0 );
119121 }
120122
121123 /**
@@ -125,7 +127,20 @@ public List<WebElement> findElements() {
125127 if (cachedElementList != null && shouldCache ) {
126128 return cachedElementList ;
127129 }
128- List <WebElement > result = waitFor ();
130+
131+ List <WebElement > result ;
132+ try {
133+ result = waitFor (() -> {
134+ List <WebElement > list = searchContext .findElements (by );
135+ if (list .size () > 0 ) {
136+ return list ;
137+ }
138+ return null ;
139+ });
140+ } catch (TimeoutException | StaleElementReferenceException e ) {
141+ result = new ArrayList <>();
142+ }
143+
129144 if (shouldCache ) {
130145 cachedElementList = result ;
131146 }
@@ -138,26 +153,19 @@ public List<WebElement> findElements() {
138153
139154
140155 // This function waits for not empty element list using all defined by
141- private static class WaitingFunction implements Function <By , List <WebElement >> {
142- private final SearchContext searchContext ;
143- Throwable foundStaleElementReferenceException ;
144-
145- private WaitingFunction (SearchContext searchContext ) {
146- this .searchContext = searchContext ;
147- }
156+ private static class WaitingFunction <T > implements Function <Supplier <T >, T > {
157+ private Throwable foundStaleElementReferenceException ;
148158
149- public List <WebElement > apply (By by ) {
150- List <WebElement > result = new ArrayList <>();
151- Throwable shouldBeThrown = null ;
152- boolean isRootCauseInvalidSelector ;
153- boolean isRootCauseStaleElementReferenceException = false ;
159+ public T apply (Supplier <T > supplier ) {
154160 foundStaleElementReferenceException = null ;
155161
156162 try {
157- result . addAll ( searchContext . findElements ( by ) );
163+ return supplier . get ( );
158164 } catch (Throwable e ) {
165+ boolean isRootCauseStaleElementReferenceException = false ;
166+ Throwable shouldBeThrown ;
167+ boolean isRootCauseInvalidSelector = isInvalidSelectorRootCause (e );
159168
160- isRootCauseInvalidSelector = isInvalidSelectorRootCause (e );
161169 if (!isRootCauseInvalidSelector ) {
162170 isRootCauseStaleElementReferenceException = isStaleElementReferenceException (e );
163171 }
@@ -168,21 +176,19 @@ public List<WebElement> apply(By by) {
168176
169177 if (!isRootCauseInvalidSelector & !isRootCauseStaleElementReferenceException ) {
170178 shouldBeThrown = extractReadableException (e );
179+ if (shouldBeThrown != null ) {
180+ if (NoSuchElementException .class .equals (shouldBeThrown .getClass ())) {
181+ throw NoSuchElementException .class .cast (shouldBeThrown );
182+ } else {
183+ throw new WebDriverException (shouldBeThrown );
184+ }
185+ } else {
186+ throw new WebDriverException (e );
187+ }
188+ } else {
189+ return null ;
171190 }
172191 }
173-
174- if (shouldBeThrown != null ) {
175- if (RuntimeException .class .isAssignableFrom (shouldBeThrown .getClass ())) {
176- throw (RuntimeException ) shouldBeThrown ;
177- }
178- throw new RuntimeException (shouldBeThrown );
179- }
180-
181- if (result .size () > 0 ) {
182- return result ;
183- } else {
184- return null ;
185- }
186192 }
187193 }
188194}
0 commit comments