Skip to content

Commit a45dff7

Browse files
Merge pull request #551 from TikhomirovSergey/master
#549 FIX
2 parents d2fbac2 + b5acb64 commit a45dff7

File tree

5 files changed

+153
-51
lines changed

5 files changed

+153
-51
lines changed

src/main/java/io/appium/java_client/pagefactory/AppiumElementLocator.java

Lines changed: 56 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -28,14 +28,17 @@
2828
import org.openqa.selenium.By;
2929
import org.openqa.selenium.NoSuchElementException;
3030
import org.openqa.selenium.SearchContext;
31+
import org.openqa.selenium.StaleElementReferenceException;
3132
import org.openqa.selenium.TimeoutException;
3233
import org.openqa.selenium.WebDriver;
34+
import org.openqa.selenium.WebDriverException;
3335
import org.openqa.selenium.WebElement;
3436
import org.openqa.selenium.support.ui.FluentWait;
3537

3638
import java.util.ArrayList;
3739
import java.util.List;
3840
import java.util.concurrent.TimeUnit;
41+
import java.util.function.Supplier;
3942

4043
class 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
}

src/main/java/io/appium/java_client/pagefactory/bys/ContentMappedBy.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,10 @@ public ContentMappedBy(Map<ContentType, By> map) {
3232
this.map = map;
3333
}
3434

35+
@Override public WebElement findElement(SearchContext context) {
36+
return context.findElement(map.get(getCurrentContentType(context)));
37+
}
38+
3539
@Override public List<WebElement> findElements(SearchContext context) {
3640
return context.findElements(map.get(getCurrentContentType(context)));
3741
}

src/main/java/io/appium/java_client/pagefactory/bys/builder/AppiumByBuilder.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@
2525
import org.openqa.selenium.By;
2626
import org.openqa.selenium.support.pagefactory.AbstractAnnotations;
2727
import org.openqa.selenium.support.pagefactory.ByAll;
28-
import org.openqa.selenium.support.pagefactory.ByChained;
2928

3029
import java.lang.annotation.Annotation;
3130
import java.lang.reflect.AnnotatedElement;
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
/*
2+
* Licensed under the Apache License, Version 2.0 (the "License");
3+
* you may not use this file except in compliance with the License.
4+
* See the NOTICE file distributed with this work for additional
5+
* information regarding copyright ownership.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package io.appium.java_client.pagefactory.bys.builder;
18+
19+
import static com.google.common.base.Preconditions.checkNotNull;
20+
21+
import io.appium.java_client.functions.AppiumFunction;
22+
import org.openqa.selenium.By;
23+
import org.openqa.selenium.NoSuchElementException;
24+
import org.openqa.selenium.SearchContext;
25+
import org.openqa.selenium.TimeoutException;
26+
import org.openqa.selenium.WebElement;
27+
import org.openqa.selenium.support.ui.FluentWait;
28+
29+
import java.util.Optional;
30+
31+
class ByChained extends org.openqa.selenium.support.pagefactory.ByChained {
32+
33+
private final By[] bys;
34+
35+
private static AppiumFunction<SearchContext, WebElement> getSearchingFunction(By by) {
36+
return input -> {
37+
try {
38+
return input.findElement(by);
39+
} catch (NoSuchElementException e) {
40+
return null;
41+
}
42+
};
43+
}
44+
45+
public ByChained(By[] bys) {
46+
super(bys);
47+
checkNotNull(bys);
48+
if (bys.length == 0) {
49+
throw new IllegalArgumentException("By array should not be empty");
50+
}
51+
this.bys = bys;
52+
}
53+
54+
@Override
55+
public WebElement findElement(SearchContext context) {
56+
AppiumFunction<SearchContext, WebElement> searchingFunction = null;
57+
58+
for (By by: bys) {
59+
searchingFunction = Optional.ofNullable(searchingFunction != null
60+
? searchingFunction.andThen(getSearchingFunction(by)) : null).orElse(getSearchingFunction(by));
61+
}
62+
63+
FluentWait<SearchContext> waiting = new FluentWait<>(context);
64+
65+
try {
66+
checkNotNull(searchingFunction);
67+
return waiting.until(searchingFunction);
68+
} catch (TimeoutException e) {
69+
throw new NoSuchElementException("Cannot locate an element using " + toString());
70+
}
71+
}
72+
}

src/test/java/io/appium/java_client/pagefactory_tests/AndroidPageObjectTest.java

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,18 @@ public class AndroidPageObjectTest extends BaseAndroidTest {
168168
@FindBy(className = "android.widget.TextView")
169169
private MobileElement cached;
170170

171+
@AndroidFindBy(uiAutomator = "new UiSelector().resourceId(\"android:id/content\")")
172+
@AndroidFindBy(uiAutomator = "new UiSelector().resourceId(\"android:id/list\")")
173+
@AndroidFindBy(id = "android:id/Faketext1")
174+
@AndroidFindBy(id = "android:id/text1")
175+
private WebElement elementFoundByInvalidChainedSelector;
176+
177+
@AndroidFindBy(uiAutomator = "new UiSelector().resourceId(\"android:id/content\")")
178+
@AndroidFindBy(uiAutomator = "new UiSelector().resourceId(\"android:id/list\")")
179+
@AndroidFindBy(id = "android:id/Faketext1")
180+
@AndroidFindBy(id = "android:id/text1")
181+
private List<WebElement> elementsFoundByInvalidChainedSelector;
182+
171183
/**
172184
* The setting up.
173185
*/
@@ -312,4 +324,13 @@ public class AndroidPageObjectTest extends BaseAndroidTest {
312324
@Test public void checkCached() {
313325
assertEquals(cached.getId(), cached.getId());
314326
}
327+
328+
@Test(expected = NoSuchElementException.class)
329+
public void checkThatElementSearchingThrowsExpectedExceptionIfChainedLocatorIsInvalid() {
330+
assertNotNull(elementFoundByInvalidChainedSelector.getAttribute("text"));
331+
}
332+
333+
@Test public void checkThatListSearchingWorksIfChainedLocatorIsInvalid() {
334+
assertEquals(0, elementsFoundByInvalidChainedSelector.size());
335+
}
315336
}

0 commit comments

Comments
 (0)