From 030c897a14d731535b3b3f0954c15799a87a652a Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Thu, 19 Oct 2023 22:50:25 +0200 Subject: [PATCH 1/5] fix: Properly unwrap driver instance if the ContextAware object is deeply nested --- .../pagefactory/utils/WebDriverUnpackUtility.java | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/src/main/java/io/appium/java_client/pagefactory/utils/WebDriverUnpackUtility.java b/src/main/java/io/appium/java_client/pagefactory/utils/WebDriverUnpackUtility.java index 01cc25a7e..8893613a3 100644 --- a/src/main/java/io/appium/java_client/pagefactory/utils/WebDriverUnpackUtility.java +++ b/src/main/java/io/appium/java_client/pagefactory/utils/WebDriverUnpackUtility.java @@ -51,21 +51,16 @@ private WebDriverUnpackUtility() { */ @Nullable public static WebDriver unpackWebDriverFromSearchContext(SearchContext searchContext) { - if (searchContext instanceof WebDriver) { - return (WebDriver) searchContext; - } - + // ! The sequence is important here if (searchContext instanceof WrapsDriver) { return unpackWebDriverFromSearchContext(((WrapsDriver) searchContext).getWrappedDriver()); } - // Search context it is not only WebDriver. WebElement is search context too. // RemoteWebElement implements WrapsDriver if (searchContext instanceof WrapsElement) { return unpackWebDriverFromSearchContext(((WrapsElement) searchContext).getWrappedElement()); } - - return null; + return (searchContext instanceof WebDriver) ? (WebDriver) searchContext : null; } /** @@ -88,9 +83,8 @@ public static ContentType getCurrentContentType(SearchContext context) { return NATIVE_MOBILE_SPECIFIC; } - if (ContextAware.class.isAssignableFrom(driver.getClass())) { //it is desktop browser - ContextAware contextAware = (ContextAware) driver; - var currentContext = contextAware.getContext(); + if (driver instanceof ContextAware) { + var currentContext = ((ContextAware) driver ).getContext(); if (currentContext != null && currentContext.toUpperCase().contains(NATIVE_APP_PATTERN)) { return NATIVE_MOBILE_SPECIFIC; } From 856ecd58696b30e2565b3cd0ef61558da82ede0c Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Fri, 20 Oct 2023 01:06:53 +0200 Subject: [PATCH 2/5] Simplify --- .../pagefactory/AppiumFieldDecorator.java | 46 ++++++------ .../utils/WebDriverUnpackUtility.java | 70 ++++++++++++------- 2 files changed, 67 insertions(+), 49 deletions(-) diff --git a/src/main/java/io/appium/java_client/pagefactory/AppiumFieldDecorator.java b/src/main/java/io/appium/java_client/pagefactory/AppiumFieldDecorator.java index 67af93096..cba54b4a4 100644 --- a/src/main/java/io/appium/java_client/pagefactory/AppiumFieldDecorator.java +++ b/src/main/java/io/appium/java_client/pagefactory/AppiumFieldDecorator.java @@ -19,7 +19,6 @@ import io.appium.java_client.internal.CapabilityHelpers; import io.appium.java_client.pagefactory.bys.ContentType; import io.appium.java_client.pagefactory.locator.CacheableLocator; -import org.openqa.selenium.Capabilities; import org.openqa.selenium.HasCapabilities; import org.openqa.selenium.SearchContext; import org.openqa.selenium.WebDriver; @@ -44,8 +43,10 @@ import java.util.Collections; import java.util.List; import java.util.Map; +import java.util.Optional; import static io.appium.java_client.pagefactory.utils.ProxyFactory.getEnhancedProxy; +import static io.appium.java_client.pagefactory.utils.WebDriverUnpackUtility.unpackObjectFromSearchContext; import static io.appium.java_client.pagefactory.utils.WebDriverUnpackUtility.unpackWebDriverFromSearchContext; import static io.appium.java_client.remote.options.SupportsAutomationNameOption.AUTOMATION_NAME_OPTION; import static java.time.Duration.ofSeconds; @@ -82,23 +83,15 @@ public class AppiumFieldDecorator implements FieldDecorator { * @param duration is a desired duration of the waiting for an element presence. */ public AppiumFieldDecorator(SearchContext context, Duration duration) { - WebDriver wd = unpackWebDriverFromSearchContext(context); - this.webDriverReference = wd == null ? null : new WeakReference<>(wd); - if (wd instanceof HasCapabilities) { - Capabilities caps = ((HasCapabilities) wd).getCapabilities(); - this.platform = CapabilityHelpers.getCapability(caps, CapabilityType.PLATFORM_NAME, String.class); - this.automation = CapabilityHelpers.getCapability(caps, AUTOMATION_NAME_OPTION, String.class); - } else { - this.platform = null; - this.automation = null; - } - + this.webDriverReference = Optional.ofNullable(unpackWebDriverFromSearchContext(context)) + .map(WeakReference::new).orElse(null); + this.platform = readStringCapability(context, CapabilityType.PLATFORM_NAME); + this.automation = readStringCapability(context, AUTOMATION_NAME_OPTION); this.duration = duration; defaultElementFieldDecorator = createFieldDecorator(new AppiumElementLocatorFactory( context, duration, new DefaultElementByBuilder(platform, automation) )); - widgetLocatorFactory = new AppiumElementLocatorFactory( context, duration, new WidgetByBuilder(platform, automation) ); @@ -117,28 +110,31 @@ public AppiumFieldDecorator(SearchContext context) { * @param duration is a desired duration of the waiting for an element presence. */ AppiumFieldDecorator(WeakReference contextReference, Duration duration) { - WebDriver wd = unpackWebDriverFromSearchContext(contextReference.get()); - this.webDriverReference = wd == null ? null : new WeakReference<>(wd); - if (wd instanceof HasCapabilities) { - Capabilities caps = ((HasCapabilities) wd).getCapabilities(); - this.platform = CapabilityHelpers.getCapability(caps, CapabilityType.PLATFORM_NAME, String.class); - this.automation = CapabilityHelpers.getCapability(caps, AUTOMATION_NAME_OPTION, String.class); - } else { - this.platform = null; - this.automation = null; - } - + var cr = contextReference.get(); + this.webDriverReference = Optional.ofNullable(unpackWebDriverFromSearchContext(cr)) + .map(WeakReference::new).orElse(null); + this.platform = readStringCapability(cr, CapabilityType.PLATFORM_NAME); + this.automation = readStringCapability(cr, AUTOMATION_NAME_OPTION); this.duration = duration; defaultElementFieldDecorator = createFieldDecorator(new AppiumElementLocatorFactory( contextReference, duration, new DefaultElementByBuilder(platform, automation) )); - widgetLocatorFactory = new AppiumElementLocatorFactory( contextReference, duration, new WidgetByBuilder(platform, automation) ); } + private String readStringCapability(SearchContext searchContext, String capName) { + if (searchContext == null) { + return null; + } + return unpackObjectFromSearchContext(searchContext, HasCapabilities.class) + .map(HasCapabilities::getCapabilities) + .map(caps -> CapabilityHelpers.getCapability(caps, capName, String.class)) + .orElse(null); + } + private DefaultFieldDecorator createFieldDecorator(ElementLocatorFactory factory) { return new DefaultFieldDecorator(factory) { @Override diff --git a/src/main/java/io/appium/java_client/pagefactory/utils/WebDriverUnpackUtility.java b/src/main/java/io/appium/java_client/pagefactory/utils/WebDriverUnpackUtility.java index 8893613a3..5c36381e2 100644 --- a/src/main/java/io/appium/java_client/pagefactory/utils/WebDriverUnpackUtility.java +++ b/src/main/java/io/appium/java_client/pagefactory/utils/WebDriverUnpackUtility.java @@ -26,9 +26,10 @@ import javax.annotation.Nullable; +import java.util.Optional; + import static io.appium.java_client.pagefactory.bys.ContentType.HTML_OR_DEFAULT; import static io.appium.java_client.pagefactory.bys.ContentType.NATIVE_MOBILE_SPECIFIC; -import static java.util.Optional.ofNullable; public final class WebDriverUnpackUtility { private static final String NATIVE_APP_PATTERN = "NATIVE_APP"; @@ -36,6 +37,38 @@ public final class WebDriverUnpackUtility { private WebDriverUnpackUtility() { } + /** + * This method extracts an instance of the given interface from the given {@link SearchContext}. + * It is expected that the {@link SearchContext} itself or the object it wraps implements it. + * + * @param searchContext is an instance of {@link SearchContext}. It may be the instance of + * {@link WebDriver} or {@link org.openqa.selenium.WebElement} or some other + * user's extension/implementation. + * Note: if you want to use your own implementation then it should implement + * {@link WrapsDriver} or {@link WrapsElement} + * @param cls interface whose instance is going to be extracted. + * @return Either an instance of the given interface or Optional.empty(). + */ + public static Optional unpackObjectFromSearchContext(@Nullable SearchContext searchContext, Class cls) { + if (searchContext == null) { + return Optional.empty(); + } + + if (searchContext.getClass().isAssignableFrom(cls)) { + return Optional.of(cls.cast(searchContext)); + } + if (searchContext instanceof WrapsDriver) { + return unpackObjectFromSearchContext(((WrapsDriver) searchContext).getWrappedDriver(), cls); + } + // Search context it is not only WebDriver. WebElement is search context too. + // RemoteWebElement implements WrapsDriver + if (searchContext instanceof WrapsElement) { + return unpackObjectFromSearchContext(((WrapsElement) searchContext).getWrappedElement(), cls); + } + + return Optional.empty(); + } + /** * This method extract an instance of {@link WebDriver} from the given {@link SearchContext}. * @param searchContext is an instance of {@link SearchContext}. It may be the instance of @@ -50,17 +83,8 @@ private WebDriverUnpackUtility() { * */ @Nullable - public static WebDriver unpackWebDriverFromSearchContext(SearchContext searchContext) { - // ! The sequence is important here - if (searchContext instanceof WrapsDriver) { - return unpackWebDriverFromSearchContext(((WrapsDriver) searchContext).getWrappedDriver()); - } - // Search context it is not only WebDriver. WebElement is search context too. - // RemoteWebElement implements WrapsDriver - if (searchContext instanceof WrapsElement) { - return unpackWebDriverFromSearchContext(((WrapsElement) searchContext).getWrappedElement()); - } - return (searchContext instanceof WebDriver) ? (WebDriver) searchContext : null; + public static WebDriver unpackWebDriverFromSearchContext(@Nullable SearchContext searchContext) { + return unpackObjectFromSearchContext(searchContext, WebDriver.class).orElse(null); } /** @@ -78,19 +102,17 @@ public static WebDriver unpackWebDriverFromSearchContext(SearchContext searchCon * {@link SearchContext} instance doesn't implement {@link ContextAware} and {@link WrapsDriver} */ public static ContentType getCurrentContentType(SearchContext context) { - return ofNullable(unpackWebDriverFromSearchContext(context)).map(driver -> { - if (driver instanceof HasBrowserCheck && !((HasBrowserCheck) driver).isBrowser()) { - return NATIVE_MOBILE_SPECIFIC; - } + var browserCheckHolder = unpackObjectFromSearchContext(context, HasBrowserCheck.class); + if (browserCheckHolder.filter(HasBrowserCheck::isBrowser).isPresent()) { + return NATIVE_MOBILE_SPECIFIC; + } - if (driver instanceof ContextAware) { - var currentContext = ((ContextAware) driver ).getContext(); - if (currentContext != null && currentContext.toUpperCase().contains(NATIVE_APP_PATTERN)) { - return NATIVE_MOBILE_SPECIFIC; - } - } + var contextAware = unpackObjectFromSearchContext(context, ContextAware.class); + if (contextAware.map(ContextAware::getContext) + .filter(c -> c.toUpperCase().contains(NATIVE_APP_PATTERN)).isPresent()) { + return NATIVE_MOBILE_SPECIFIC; + } - return HTML_OR_DEFAULT; - }).orElse(HTML_OR_DEFAULT); + return HTML_OR_DEFAULT; } } From 9174f426e8f64218e26316138f47d92cd238825c Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Fri, 20 Oct 2023 01:08:04 +0200 Subject: [PATCH 3/5] Add nullable --- .../io/appium/java_client/pagefactory/AppiumFieldDecorator.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/io/appium/java_client/pagefactory/AppiumFieldDecorator.java b/src/main/java/io/appium/java_client/pagefactory/AppiumFieldDecorator.java index cba54b4a4..86c609b0a 100644 --- a/src/main/java/io/appium/java_client/pagefactory/AppiumFieldDecorator.java +++ b/src/main/java/io/appium/java_client/pagefactory/AppiumFieldDecorator.java @@ -125,6 +125,7 @@ contextReference, duration, new WidgetByBuilder(platform, automation) ); } + @Nullable private String readStringCapability(SearchContext searchContext, String capName) { if (searchContext == null) { return null; From d27bc9263a204f2edec453ca8bbb33f23e08d6db Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Fri, 20 Oct 2023 10:02:54 +0200 Subject: [PATCH 4/5] Fix condition --- .../java_client/pagefactory/utils/WebDriverUnpackUtility.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/io/appium/java_client/pagefactory/utils/WebDriverUnpackUtility.java b/src/main/java/io/appium/java_client/pagefactory/utils/WebDriverUnpackUtility.java index 5c36381e2..f2649105f 100644 --- a/src/main/java/io/appium/java_client/pagefactory/utils/WebDriverUnpackUtility.java +++ b/src/main/java/io/appium/java_client/pagefactory/utils/WebDriverUnpackUtility.java @@ -54,7 +54,7 @@ public static Optional unpackObjectFromSearchContext(@Nullable SearchCont return Optional.empty(); } - if (searchContext.getClass().isAssignableFrom(cls)) { + if (cls.isAssignableFrom(searchContext.getClass())) { return Optional.of(cls.cast(searchContext)); } if (searchContext instanceof WrapsDriver) { From 3eb876d4207ea399b3e7e90da7641c05742d42f2 Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Fri, 20 Oct 2023 16:03:50 +0200 Subject: [PATCH 5/5] Fix condition --- .../java_client/pagefactory/AppiumFieldDecorator.java | 6 ++---- .../pagefactory/utils/WebDriverUnpackUtility.java | 2 +- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/main/java/io/appium/java_client/pagefactory/AppiumFieldDecorator.java b/src/main/java/io/appium/java_client/pagefactory/AppiumFieldDecorator.java index 86c609b0a..280cd02d1 100644 --- a/src/main/java/io/appium/java_client/pagefactory/AppiumFieldDecorator.java +++ b/src/main/java/io/appium/java_client/pagefactory/AppiumFieldDecorator.java @@ -43,11 +43,9 @@ import java.util.Collections; import java.util.List; import java.util.Map; -import java.util.Optional; import static io.appium.java_client.pagefactory.utils.ProxyFactory.getEnhancedProxy; import static io.appium.java_client.pagefactory.utils.WebDriverUnpackUtility.unpackObjectFromSearchContext; -import static io.appium.java_client.pagefactory.utils.WebDriverUnpackUtility.unpackWebDriverFromSearchContext; import static io.appium.java_client.remote.options.SupportsAutomationNameOption.AUTOMATION_NAME_OPTION; import static java.time.Duration.ofSeconds; @@ -83,7 +81,7 @@ public class AppiumFieldDecorator implements FieldDecorator { * @param duration is a desired duration of the waiting for an element presence. */ public AppiumFieldDecorator(SearchContext context, Duration duration) { - this.webDriverReference = Optional.ofNullable(unpackWebDriverFromSearchContext(context)) + this.webDriverReference = unpackObjectFromSearchContext(context, WebDriver.class) .map(WeakReference::new).orElse(null); this.platform = readStringCapability(context, CapabilityType.PLATFORM_NAME); this.automation = readStringCapability(context, AUTOMATION_NAME_OPTION); @@ -111,7 +109,7 @@ public AppiumFieldDecorator(SearchContext context) { */ AppiumFieldDecorator(WeakReference contextReference, Duration duration) { var cr = contextReference.get(); - this.webDriverReference = Optional.ofNullable(unpackWebDriverFromSearchContext(cr)) + this.webDriverReference = unpackObjectFromSearchContext(cr, WebDriver.class) .map(WeakReference::new).orElse(null); this.platform = readStringCapability(cr, CapabilityType.PLATFORM_NAME); this.automation = readStringCapability(cr, AUTOMATION_NAME_OPTION); diff --git a/src/main/java/io/appium/java_client/pagefactory/utils/WebDriverUnpackUtility.java b/src/main/java/io/appium/java_client/pagefactory/utils/WebDriverUnpackUtility.java index f2649105f..190f9c4ae 100644 --- a/src/main/java/io/appium/java_client/pagefactory/utils/WebDriverUnpackUtility.java +++ b/src/main/java/io/appium/java_client/pagefactory/utils/WebDriverUnpackUtility.java @@ -103,7 +103,7 @@ public static WebDriver unpackWebDriverFromSearchContext(@Nullable SearchContext */ public static ContentType getCurrentContentType(SearchContext context) { var browserCheckHolder = unpackObjectFromSearchContext(context, HasBrowserCheck.class); - if (browserCheckHolder.filter(HasBrowserCheck::isBrowser).isPresent()) { + if (browserCheckHolder.filter(hbc -> !hbc.isBrowser()).isPresent()) { return NATIVE_MOBILE_SPECIFIC; }