Skip to content

Commit dd8f33e

Browse files
Merge pull request #769 from TikhomirovSergey/#764_FIX
#764 alternative fix
2 parents 5dab23d + d10f5d1 commit dd8f33e

File tree

8 files changed

+100
-61
lines changed

8 files changed

+100
-61
lines changed

src/main/java/io/appium/java_client/DefaultGenericMobileDriver.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,7 @@ public List findElementsByXPath(String using) {
153153

154154
@Override
155155
public String toString() {
156-
return String.format("%s: %s", getPlatformName(),
157-
getAutomationName());
156+
return String.format("%s, Capabilities: %s", getClass().getCanonicalName(),
157+
getCapabilities().asMap().toString());
158158
}
159159
}

src/main/java/io/appium/java_client/internal/ElementMap.java

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,10 @@
1616

1717
package io.appium.java_client.internal;
1818

19+
import static org.apache.commons.lang3.StringUtils.isBlank;
20+
1921
import com.google.common.collect.ImmutableMap;
2022

21-
import io.appium.java_client.HasSessionDetails;
2223
import io.appium.java_client.MobileElement;
2324
import io.appium.java_client.android.AndroidElement;
2425
import io.appium.java_client.ios.IOSElement;
@@ -68,17 +69,19 @@ public Class<? extends RemoteWebElement> getElementClass() {
6869
}
6970

7071
/**
71-
* @param hasSessionDetails something that implements {@link io.appium.java_client.HasSessionDetails}.
72-
* @return subclass of {@link io.appium.java_client.MobileElement} that convenient to current session details.
72+
* @param platform is the mobile platform. See {@link MobilePlatform}.
73+
* @param automation is the mobile automation type. See {@link AutomationName}
74+
* @return subclass of {@link org.openqa.selenium.remote.RemoteWebElement} that convenient
75+
* to current session details.
7376
*/
74-
public static Class<? extends RemoteWebElement> getElementClass(HasSessionDetails hasSessionDetails) {
75-
if (hasSessionDetails == null) {
77+
public static Class<? extends RemoteWebElement> getElementClass(String platform, String automation) {
78+
if (isBlank(platform) && isBlank(automation)) {
7679
return RemoteWebElement.class;
7780
}
78-
ElementMap element = Optional.ofNullable(mobileElementMap.get(String
79-
.valueOf(hasSessionDetails.getAutomationName()).toLowerCase().trim()))
81+
ElementMap element = Optional.ofNullable(mobileElementMap.get(
82+
String.valueOf(platform).toLowerCase().trim()))
8083
.orElseGet(() -> mobileElementMap
81-
.get(String.valueOf(hasSessionDetails.getPlatformName()).toLowerCase().trim()));
84+
.get(String.valueOf(automation).toLowerCase().trim()));
8285
if (element == null) {
8386
return RemoteWebElement.class;
8487
}

src/main/java/io/appium/java_client/internal/JsonToMobileElementConverter.java

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,10 @@
3737
public class JsonToMobileElementConverter extends JsonToWebElementConverter {
3838

3939
protected final RemoteWebDriver driver;
40-
private final HasSessionDetails hasSessionDetails;
40+
41+
private final String platform;
42+
private final String automation;
43+
4144

4245
/**
4346
* @param driver an instance of {@link org.openqa.selenium.remote.RemoteWebDriver} subclass
@@ -46,7 +49,8 @@ public class JsonToMobileElementConverter extends JsonToWebElementConverter {
4649
public JsonToMobileElementConverter(RemoteWebDriver driver, HasSessionDetails hasSessionDetails) {
4750
super(driver);
4851
this.driver = driver;
49-
this.hasSessionDetails = hasSessionDetails;
52+
this.platform = hasSessionDetails.getPlatformName();
53+
this.automation = hasSessionDetails.getAutomationName();
5054
}
5155

5256
/**
@@ -80,9 +84,9 @@ public Object apply(Object result) {
8084
return result;
8185
}
8286

83-
protected RemoteWebElement newMobileElement() {
87+
private RemoteWebElement newMobileElement() {
8488
Class<? extends RemoteWebElement> target;
85-
target = getElementClass(hasSessionDetails);
89+
target = getElementClass(platform, automation);
8690
try {
8791
Constructor<? extends RemoteWebElement> constructor = target.getDeclaredConstructor();
8892
constructor.setAccessible(true);

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

Lines changed: 32 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,11 @@
1919
import static io.appium.java_client.pagefactory.ThrowableUtil.extractReadableException;
2020
import static io.appium.java_client.pagefactory.ThrowableUtil.isInvalidSelectorRootCause;
2121
import static io.appium.java_client.pagefactory.ThrowableUtil.isStaleElementReferenceException;
22+
import static io.appium.java_client.pagefactory.utils.WebDriverUnpackUtility.getCurrentContentType;
23+
import static java.lang.String.format;
2224

2325

26+
import io.appium.java_client.pagefactory.bys.ContentMappedBy;
2427
import io.appium.java_client.pagefactory.locator.CacheableLocator;
2528

2629
import org.openqa.selenium.By;
@@ -39,13 +42,15 @@
3942

4043
class AppiumElementLocator implements CacheableLocator {
4144

45+
private static final String exceptionMessageIfElementNotFound = "Can't locate an element by this strategy: %s";
46+
4247
private final boolean shouldCache;
4348
private final By by;
4449
private final TimeOutDuration duration;
4550
private final SearchContext searchContext;
4651
private WebElement cachedElement;
4752
private List<WebElement> cachedElementList;
48-
private final String exceptionMessageIfElementNotFound;
53+
4954
/**
5055
* Creates a new mobile element locator. It instantiates {@link WebElement}
5156
* using @AndroidFindBy (-s), @iOSFindBy (-s) and @FindBy (-s) annotation
@@ -64,7 +69,25 @@ public AppiumElementLocator(SearchContext searchContext, By by, boolean shouldCa
6469
this.shouldCache = shouldCache;
6570
this.duration = duration;
6671
this.by = by;
67-
this.exceptionMessageIfElementNotFound = "Can't locate an element by this strategy: " + by.toString();
72+
}
73+
74+
/**
75+
* This methods makes sets some settings of the {@link By} according to
76+
* the given instance of {@link SearchContext}. If there is some {@link ContentMappedBy}
77+
* then it is switched to the searching for some html or native mobile element.
78+
* Otherwise nothing happens there.
79+
*
80+
* @param currentBy is some locator strategy
81+
* @param currentContent is an instance of some subclass of the {@link SearchContext}.
82+
* @return the corrected {@link By} for the further searching
83+
*/
84+
private static By getBy(By currentBy, SearchContext currentContent) {
85+
if (!ContentMappedBy.class.isAssignableFrom(currentBy.getClass())) {
86+
return currentBy;
87+
}
88+
89+
return ContentMappedBy.class.cast(currentBy)
90+
.useContent(getCurrentContentType(currentContent));
6891
}
6992

7093
private <T> T waitFor(Supplier<T> supplier) {
@@ -91,15 +114,16 @@ public WebElement findElement() {
91114
return cachedElement;
92115
}
93116

117+
By bySearching = getBy(this.by, searchContext);
94118
try {
95119
WebElement result = waitFor(() ->
96-
searchContext.findElement(by));
120+
searchContext.findElement(bySearching));
97121
if (shouldCache) {
98122
cachedElement = result;
99123
}
100124
return result;
101125
} catch (TimeoutException | StaleElementReferenceException e) {
102-
throw new NoSuchElementException(exceptionMessageIfElementNotFound, e);
126+
throw new NoSuchElementException(format(exceptionMessageIfElementNotFound, bySearching.toString()), e);
103127
}
104128
}
105129

@@ -114,11 +138,9 @@ public List<WebElement> findElements() {
114138
List<WebElement> result;
115139
try {
116140
result = waitFor(() -> {
117-
List<WebElement> list = searchContext.findElements(by);
118-
if (list.size() > 0) {
119-
return list;
120-
}
121-
return null;
141+
List<WebElement> list = searchContext
142+
.findElements(getBy(by, searchContext));
143+
return list.size() > 0 ? list : null;
122144
});
123145
} catch (TimeoutException | StaleElementReferenceException e) {
124146
result = new ArrayList<>();
@@ -135,7 +157,7 @@ public List<WebElement> findElements() {
135157
}
136158

137159
@Override public String toString() {
138-
return String.format("Located by %s", by);
160+
return format("Located by %s", by);
139161
}
140162

141163

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

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import static io.appium.java_client.pagefactory.utils.ProxyFactory.getEnhancedProxy;
2121
import static io.appium.java_client.pagefactory.utils.WebDriverUnpackUtility
2222
.unpackWebDriverFromSearchContext;
23+
import static java.util.Optional.ofNullable;
2324

2425
import com.google.common.collect.ImmutableList;
2526

@@ -64,13 +65,12 @@ public class AppiumFieldDecorator implements FieldDecorator {
6465
IOSElement.class, WindowsElement.class);
6566
public static long DEFAULT_TIMEOUT = 1;
6667
public static TimeUnit DEFAULT_TIMEUNIT = TimeUnit.SECONDS;
67-
private final WebDriver originalDriver;
68+
private final WebDriver webDriver;
6869
private final DefaultFieldDecorator defaultElementFieldDecoracor;
6970
private final AppiumElementLocatorFactory widgetLocatorFactory;
7071
private final String platform;
7172
private final String automation;
7273
private final TimeOutDuration duration;
73-
private final HasSessionDetails hasSessionDetails;
7474

7575

7676
public AppiumFieldDecorator(SearchContext context, long timeout,
@@ -87,14 +87,18 @@ public AppiumFieldDecorator(SearchContext context, long timeout,
8787
* @param duration is a desired duration of the waiting for an element presence.
8888
*/
8989
public AppiumFieldDecorator(SearchContext context, TimeOutDuration duration) {
90-
this.originalDriver = unpackWebDriverFromSearchContext(context);
91-
if (originalDriver == null
92-
|| !HasSessionDetails.class.isAssignableFrom(originalDriver.getClass())) {
93-
hasSessionDetails = null;
90+
this.webDriver = unpackWebDriverFromSearchContext(context);
91+
HasSessionDetails hasSessionDetails = ofNullable(this.webDriver).map(webDriver -> {
92+
if (!HasSessionDetails.class.isAssignableFrom(webDriver.getClass())) {
93+
return null;
94+
}
95+
return HasSessionDetails.class.cast(webDriver);
96+
}).orElse(null);
97+
98+
if (hasSessionDetails == null) {
9499
platform = null;
95100
automation = null;
96101
} else {
97-
hasSessionDetails = HasSessionDetails.class.cast(originalDriver);
98102
platform = hasSessionDetails.getPlatformName();
99103
automation = hasSessionDetails.getAutomationName();
100104
}
@@ -202,19 +206,19 @@ private Object decorateWidget(Field field) {
202206

203207
if (isAlist) {
204208
return getEnhancedProxy(ArrayList.class,
205-
new WidgetListInterceptor(locator, originalDriver, map, widgetType,
209+
new WidgetListInterceptor(locator, webDriver, map, widgetType,
206210
duration));
207211
}
208212

209213
Constructor<? extends Widget> constructor =
210214
WidgetConstructorUtil.findConvenientConstructor(widgetType);
211215
return getEnhancedProxy(widgetType, new Class[] {constructor.getParameterTypes()[0]},
212216
new Object[] {proxyForAnElement(locator)},
213-
new WidgetInterceptor(locator, originalDriver, null, map, duration));
217+
new WidgetInterceptor(locator, webDriver, null, map, duration));
214218
}
215219

216220
private WebElement proxyForAnElement(ElementLocator locator) {
217-
ElementInterceptor elementInterceptor = new ElementInterceptor(locator, originalDriver);
218-
return getEnhancedProxy(getElementClass(hasSessionDetails), elementInterceptor);
221+
ElementInterceptor elementInterceptor = new ElementInterceptor(locator, webDriver);
222+
return getEnhancedProxy(getElementClass(platform, automation), elementInterceptor);
219223
}
220224
}

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
import static io.appium.java_client.pagefactory.ThrowableUtil.extractReadableException;
2020
import static io.appium.java_client.pagefactory.utils.WebDriverUnpackUtility.getCurrentContentType;
21+
import static java.util.Optional.ofNullable;
2122

2223
import io.appium.java_client.pagefactory.bys.ContentType;
2324
import io.appium.java_client.pagefactory.interceptors.InterceptorOfAListOfElements;
@@ -59,8 +60,9 @@ class WidgetListInterceptor extends InterceptorOfAListOfElements {
5960
cachedElements = elements;
6061
cachedWidgets.clear();
6162

63+
ContentType type = null;
6264
for (WebElement element : cachedElements) {
63-
ContentType type = getCurrentContentType(element);
65+
type = ofNullable(type).orElseGet(() -> getCurrentContentType(element));
6466
Class<?>[] params =
6567
new Class<?>[] {instantiationMap.get(type).getParameterTypes()[0]};
6668
cachedWidgets.add(ProxyFactory

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

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -16,40 +16,45 @@
1616

1717
package io.appium.java_client.pagefactory.bys;
1818

19-
import static io.appium.java_client.pagefactory.utils.WebDriverUnpackUtility.getCurrentContentType;
19+
import static com.google.common.base.Preconditions.checkNotNull;
20+
import static io.appium.java_client.pagefactory.bys.ContentType.NATIVE_MOBILE_SPECIFIC;
2021

2122
import org.openqa.selenium.By;
2223
import org.openqa.selenium.SearchContext;
2324
import org.openqa.selenium.WebElement;
2425

2526
import java.util.List;
2627
import java.util.Map;
28+
import javax.annotation.Nonnull;
2729

2830
public class ContentMappedBy extends By {
2931
private final Map<ContentType, By> map;
32+
private ContentType currentContent = NATIVE_MOBILE_SPECIFIC;
3033

3134
public ContentMappedBy(Map<ContentType, By> map) {
3235
this.map = map;
3336
}
3437

38+
/**
39+
* This method sets required content type for the further searching.
40+
* @param type required content type {@link ContentType}
41+
* @return self-reference.
42+
*/
43+
public By useContent(@Nonnull ContentType type) {
44+
checkNotNull(type);
45+
currentContent = type;
46+
return this;
47+
}
48+
3549
@Override public WebElement findElement(SearchContext context) {
36-
return context.findElement(map.get(getCurrentContentType(context)));
50+
return context.findElement(map.get(currentContent));
3751
}
3852

3953
@Override public List<WebElement> findElements(SearchContext context) {
40-
return context.findElements(map.get(getCurrentContentType(context)));
54+
return context.findElements(map.get(currentContent));
4155
}
4256

4357
@Override public String toString() {
44-
By defaultBy = map.get(ContentType.HTML_OR_DEFAULT);
45-
By nativeBy = map.get(ContentType.NATIVE_MOBILE_SPECIFIC);
46-
47-
if (defaultBy.equals(nativeBy)) {
48-
return defaultBy.toString();
49-
}
50-
51-
return "Locator map: " + "\n"
52-
+ "- native content: \"" + nativeBy.toString() + "\" \n"
53-
+ "- html content: \"" + defaultBy.toString() + "\"";
58+
return map.get(currentContent).toString();
5459
}
5560
}

src/main/java/io/appium/java_client/pagefactory/utils/WebDriverUnpackUtility.java

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import static io.appium.java_client.pagefactory.bys.ContentType.HTML_OR_DEFAULT;
2020
import static io.appium.java_client.pagefactory.bys.ContentType.NATIVE_MOBILE_SPECIFIC;
2121
import static java.util.Optional.ofNullable;
22+
import static org.apache.commons.lang3.StringUtils.containsIgnoreCase;
2223

2324
import io.appium.java_client.HasSessionDetails;
2425
import io.appium.java_client.pagefactory.bys.ContentType;
@@ -77,7 +78,8 @@ public static WebDriver unpackWebDriverFromSearchContext(SearchContext searchCon
7778
* extension/implementation.
7879
* Note: if you want to use your own implementation then it should
7980
* implement {@link org.openqa.selenium.ContextAware} or
80-
* {@link org.openqa.selenium.internal.WrapsDriver}
81+
* {@link org.openqa.selenium.internal.WrapsDriver} or
82+
* {@link HasSessionDetails}
8183
* @return current content type. It depends on current context. If current context is
8284
* NATIVE_APP it will return
8385
* {@link io.appium.java_client.pagefactory.bys.ContentType#NATIVE_MOBILE_SPECIFIC}.
@@ -93,20 +95,17 @@ public static ContentType getCurrentContentType(SearchContext context) {
9395
if (HasSessionDetails.class.isAssignableFrom(driver.getClass())) {
9496
HasSessionDetails hasSessionDetails = HasSessionDetails.class.cast(driver);
9597

96-
if (hasSessionDetails.isBrowser()) {
97-
return HTML_OR_DEFAULT;
98+
if (!hasSessionDetails.isBrowser()) {
99+
return NATIVE_MOBILE_SPECIFIC;
98100
}
99-
return NATIVE_MOBILE_SPECIFIC;
100-
}
101-
102-
if (!ContextAware.class.isAssignableFrom(driver.getClass())) { //it is desktop browser
103-
return HTML_OR_DEFAULT;
104101
}
105102

106-
ContextAware contextAware = ContextAware.class.cast(driver);
107-
String currentContext = contextAware.getContext();
108-
if (currentContext.contains(NATIVE_APP_PATTERN)) {
109-
return NATIVE_MOBILE_SPECIFIC;
103+
if (ContextAware.class.isAssignableFrom(driver.getClass())) { //it is desktop browser
104+
ContextAware contextAware = ContextAware.class.cast(driver);
105+
String currentContext = contextAware.getContext();
106+
if (containsIgnoreCase(currentContext, NATIVE_APP_PATTERN)) {
107+
return NATIVE_MOBILE_SPECIFIC;
108+
}
110109
}
111110

112111
return HTML_OR_DEFAULT;

0 commit comments

Comments
 (0)