Skip to content

Commit fac489f

Browse files
fix: Properly unwrap driver instance if the ContextAware object is deeply nested (#2052)
1 parent a9240e3 commit fac489f

File tree

2 files changed

+65
-54
lines changed

2 files changed

+65
-54
lines changed

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

Lines changed: 21 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919
import io.appium.java_client.internal.CapabilityHelpers;
2020
import io.appium.java_client.pagefactory.bys.ContentType;
2121
import io.appium.java_client.pagefactory.locator.CacheableLocator;
22-
import org.openqa.selenium.Capabilities;
2322
import org.openqa.selenium.HasCapabilities;
2423
import org.openqa.selenium.SearchContext;
2524
import org.openqa.selenium.WebDriver;
@@ -46,7 +45,7 @@
4645
import java.util.Map;
4746

4847
import static io.appium.java_client.pagefactory.utils.ProxyFactory.getEnhancedProxy;
49-
import static io.appium.java_client.pagefactory.utils.WebDriverUnpackUtility.unpackWebDriverFromSearchContext;
48+
import static io.appium.java_client.pagefactory.utils.WebDriverUnpackUtility.unpackObjectFromSearchContext;
5049
import static io.appium.java_client.remote.options.SupportsAutomationNameOption.AUTOMATION_NAME_OPTION;
5150
import static java.time.Duration.ofSeconds;
5251

@@ -82,23 +81,15 @@ public class AppiumFieldDecorator implements FieldDecorator {
8281
* @param duration is a desired duration of the waiting for an element presence.
8382
*/
8483
public AppiumFieldDecorator(SearchContext context, Duration duration) {
85-
WebDriver wd = unpackWebDriverFromSearchContext(context);
86-
this.webDriverReference = wd == null ? null : new WeakReference<>(wd);
87-
if (wd instanceof HasCapabilities) {
88-
Capabilities caps = ((HasCapabilities) wd).getCapabilities();
89-
this.platform = CapabilityHelpers.getCapability(caps, CapabilityType.PLATFORM_NAME, String.class);
90-
this.automation = CapabilityHelpers.getCapability(caps, AUTOMATION_NAME_OPTION, String.class);
91-
} else {
92-
this.platform = null;
93-
this.automation = null;
94-
}
95-
84+
this.webDriverReference = unpackObjectFromSearchContext(context, WebDriver.class)
85+
.map(WeakReference::new).orElse(null);
86+
this.platform = readStringCapability(context, CapabilityType.PLATFORM_NAME);
87+
this.automation = readStringCapability(context, AUTOMATION_NAME_OPTION);
9688
this.duration = duration;
9789

9890
defaultElementFieldDecorator = createFieldDecorator(new AppiumElementLocatorFactory(
9991
context, duration, new DefaultElementByBuilder(platform, automation)
10092
));
101-
10293
widgetLocatorFactory = new AppiumElementLocatorFactory(
10394
context, duration, new WidgetByBuilder(platform, automation)
10495
);
@@ -117,28 +108,32 @@ public AppiumFieldDecorator(SearchContext context) {
117108
* @param duration is a desired duration of the waiting for an element presence.
118109
*/
119110
AppiumFieldDecorator(WeakReference<SearchContext> contextReference, Duration duration) {
120-
WebDriver wd = unpackWebDriverFromSearchContext(contextReference.get());
121-
this.webDriverReference = wd == null ? null : new WeakReference<>(wd);
122-
if (wd instanceof HasCapabilities) {
123-
Capabilities caps = ((HasCapabilities) wd).getCapabilities();
124-
this.platform = CapabilityHelpers.getCapability(caps, CapabilityType.PLATFORM_NAME, String.class);
125-
this.automation = CapabilityHelpers.getCapability(caps, AUTOMATION_NAME_OPTION, String.class);
126-
} else {
127-
this.platform = null;
128-
this.automation = null;
129-
}
130-
111+
var cr = contextReference.get();
112+
this.webDriverReference = unpackObjectFromSearchContext(cr, WebDriver.class)
113+
.map(WeakReference::new).orElse(null);
114+
this.platform = readStringCapability(cr, CapabilityType.PLATFORM_NAME);
115+
this.automation = readStringCapability(cr, AUTOMATION_NAME_OPTION);
131116
this.duration = duration;
132117

133118
defaultElementFieldDecorator = createFieldDecorator(new AppiumElementLocatorFactory(
134119
contextReference, duration, new DefaultElementByBuilder(platform, automation)
135120
));
136-
137121
widgetLocatorFactory = new AppiumElementLocatorFactory(
138122
contextReference, duration, new WidgetByBuilder(platform, automation)
139123
);
140124
}
141125

126+
@Nullable
127+
private String readStringCapability(SearchContext searchContext, String capName) {
128+
if (searchContext == null) {
129+
return null;
130+
}
131+
return unpackObjectFromSearchContext(searchContext, HasCapabilities.class)
132+
.map(HasCapabilities::getCapabilities)
133+
.map(caps -> CapabilityHelpers.getCapability(caps, capName, String.class))
134+
.orElse(null);
135+
}
136+
142137
private DefaultFieldDecorator createFieldDecorator(ElementLocatorFactory factory) {
143138
return new DefaultFieldDecorator(factory) {
144139
@Override

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

Lines changed: 44 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,10 @@
2626

2727
import javax.annotation.Nullable;
2828

29+
import java.util.Optional;
30+
2931
import static io.appium.java_client.pagefactory.bys.ContentType.HTML_OR_DEFAULT;
3032
import static io.appium.java_client.pagefactory.bys.ContentType.NATIVE_MOBILE_SPECIFIC;
31-
import static java.util.Optional.ofNullable;
3233

3334
public final class WebDriverUnpackUtility {
3435
private static final String NATIVE_APP_PATTERN = "NATIVE_APP";
@@ -37,35 +38,53 @@ private WebDriverUnpackUtility() {
3738
}
3839

3940
/**
40-
* This method extract an instance of {@link WebDriver} from the given {@link SearchContext}.
41+
* This method extracts an instance of the given interface from the given {@link SearchContext}.
42+
* It is expected that the {@link SearchContext} itself or the object it wraps implements it.
43+
*
4144
* @param searchContext is an instance of {@link SearchContext}. It may be the instance of
4245
* {@link WebDriver} or {@link org.openqa.selenium.WebElement} or some other
4346
* user's extension/implementation.
4447
* Note: if you want to use your own implementation then it should implement
4548
* {@link WrapsDriver} or {@link WrapsElement}
46-
* @return the instance of {@link WebDriver}.
47-
* Note: if the given {@link SearchContext} is not
48-
* {@link WebDriver} and it doesn't implement
49-
* {@link WrapsDriver} or {@link WrapsElement} then this method returns null.
50-
*
49+
* @param cls interface whose instance is going to be extracted.
50+
* @return Either an instance of the given interface or Optional.empty().
5151
*/
52-
@Nullable
53-
public static WebDriver unpackWebDriverFromSearchContext(SearchContext searchContext) {
54-
if (searchContext instanceof WebDriver) {
55-
return (WebDriver) searchContext;
52+
public static <T> Optional<T> unpackObjectFromSearchContext(@Nullable SearchContext searchContext, Class<T> cls) {
53+
if (searchContext == null) {
54+
return Optional.empty();
5655
}
5756

57+
if (cls.isAssignableFrom(searchContext.getClass())) {
58+
return Optional.of(cls.cast(searchContext));
59+
}
5860
if (searchContext instanceof WrapsDriver) {
59-
return unpackWebDriverFromSearchContext(((WrapsDriver) searchContext).getWrappedDriver());
61+
return unpackObjectFromSearchContext(((WrapsDriver) searchContext).getWrappedDriver(), cls);
6062
}
61-
6263
// Search context it is not only WebDriver. WebElement is search context too.
6364
// RemoteWebElement implements WrapsDriver
6465
if (searchContext instanceof WrapsElement) {
65-
return unpackWebDriverFromSearchContext(((WrapsElement) searchContext).getWrappedElement());
66+
return unpackObjectFromSearchContext(((WrapsElement) searchContext).getWrappedElement(), cls);
6667
}
6768

68-
return null;
69+
return Optional.empty();
70+
}
71+
72+
/**
73+
* This method extract an instance of {@link WebDriver} from the given {@link SearchContext}.
74+
* @param searchContext is an instance of {@link SearchContext}. It may be the instance of
75+
* {@link WebDriver} or {@link org.openqa.selenium.WebElement} or some other
76+
* user's extension/implementation.
77+
* Note: if you want to use your own implementation then it should implement
78+
* {@link WrapsDriver} or {@link WrapsElement}
79+
* @return the instance of {@link WebDriver}.
80+
* Note: if the given {@link SearchContext} is not
81+
* {@link WebDriver} and it doesn't implement
82+
* {@link WrapsDriver} or {@link WrapsElement} then this method returns null.
83+
*
84+
*/
85+
@Nullable
86+
public static WebDriver unpackWebDriverFromSearchContext(@Nullable SearchContext searchContext) {
87+
return unpackObjectFromSearchContext(searchContext, WebDriver.class).orElse(null);
6988
}
7089

7190
/**
@@ -83,20 +102,17 @@ public static WebDriver unpackWebDriverFromSearchContext(SearchContext searchCon
83102
* {@link SearchContext} instance doesn't implement {@link ContextAware} and {@link WrapsDriver}
84103
*/
85104
public static ContentType getCurrentContentType(SearchContext context) {
86-
return ofNullable(unpackWebDriverFromSearchContext(context)).map(driver -> {
87-
if (driver instanceof HasBrowserCheck && !((HasBrowserCheck) driver).isBrowser()) {
88-
return NATIVE_MOBILE_SPECIFIC;
89-
}
105+
var browserCheckHolder = unpackObjectFromSearchContext(context, HasBrowserCheck.class);
106+
if (browserCheckHolder.filter(hbc -> !hbc.isBrowser()).isPresent()) {
107+
return NATIVE_MOBILE_SPECIFIC;
108+
}
90109

91-
if (ContextAware.class.isAssignableFrom(driver.getClass())) { //it is desktop browser
92-
ContextAware contextAware = (ContextAware) driver;
93-
var currentContext = contextAware.getContext();
94-
if (currentContext != null && currentContext.toUpperCase().contains(NATIVE_APP_PATTERN)) {
95-
return NATIVE_MOBILE_SPECIFIC;
96-
}
97-
}
110+
var contextAware = unpackObjectFromSearchContext(context, ContextAware.class);
111+
if (contextAware.map(ContextAware::getContext)
112+
.filter(c -> c.toUpperCase().contains(NATIVE_APP_PATTERN)).isPresent()) {
113+
return NATIVE_MOBILE_SPECIFIC;
114+
}
98115

99-
return HTML_OR_DEFAULT;
100-
}).orElse(HTML_OR_DEFAULT);
116+
return HTML_OR_DEFAULT;
101117
}
102118
}

0 commit comments

Comments
 (0)