diff --git a/utam-core/src/main/java/utam/core/selenium/appium/MobileDriverAdapter.java b/utam-core/src/main/java/utam/core/selenium/appium/MobileDriverAdapter.java index a9a9c96e..b9a107ea 100644 --- a/utam-core/src/main/java/utam/core/selenium/appium/MobileDriverAdapter.java +++ b/utam-core/src/main/java/utam/core/selenium/appium/MobileDriverAdapter.java @@ -244,7 +244,7 @@ public boolean isNativeContext() { return MobileDriverUtils.isNative(getAppiumDriver()); } - final AppiumDriver getAppiumDriver() { + protected AppiumDriver getAppiumDriver() { return (AppiumDriver) getSeleniumDriver(); } diff --git a/utam-core/src/main/java/utam/core/selenium/factory/WebDriverFactory.java b/utam-core/src/main/java/utam/core/selenium/factory/WebDriverFactory.java index 786f8a41..18f26ce7 100644 --- a/utam-core/src/main/java/utam/core/selenium/factory/WebDriverFactory.java +++ b/utam-core/src/main/java/utam/core/selenium/factory/WebDriverFactory.java @@ -38,6 +38,7 @@ import utam.core.selenium.element.DriverAdapter; import utam.core.selenium.wrapped.WrappedDriverAdapter; import utam.core.selenium.wrapped.WrappedDriverDecorator; +import utam.core.selenium.wrapped.WrappedMobileDriverAdapter; /** * web driver factory @@ -72,12 +73,21 @@ public static WebDriver getWebDriver(DriverType browserType) { * @return the driver adapter */ public static Driver getAdapter(WebDriver webDriver, DriverConfig driverConfig) { + // Check WrappedDriverDecorator first so a wrapped AppiumDriver routes through the + // mobile-aware wrapped adapter (context switching + wrapper-routed findElement) rather than + // the plain MobileDriverAdapter (which would bypass the wrapper). + if (webDriver instanceof WrappedDriverDecorator) { + WrappedDriverDecorator wrappedDriver = (WrappedDriverDecorator) webDriver; + WebDriver unwrapped = wrappedDriver.unwrap(); + if (unwrapped instanceof AppiumDriver) { + return new WrappedMobileDriverAdapter( + wrappedDriver, (AppiumDriver) unwrapped, driverConfig); + } + return new WrappedDriverAdapter(wrappedDriver, driverConfig); + } if (webDriver instanceof AppiumDriver) { return new MobileDriverAdapter((AppiumDriver) webDriver, driverConfig); } - if (webDriver instanceof WrappedDriverDecorator) { - return new WrappedDriverAdapter((WrappedDriverDecorator) webDriver, driverConfig); - } return new DriverAdapter(webDriver, driverConfig); } diff --git a/utam-core/src/main/java/utam/core/selenium/wrapped/WrappedElementAdapter.java b/utam-core/src/main/java/utam/core/selenium/wrapped/WrappedElementAdapter.java index c139308f..7897b667 100644 --- a/utam-core/src/main/java/utam/core/selenium/wrapped/WrappedElementAdapter.java +++ b/utam-core/src/main/java/utam/core/selenium/wrapped/WrappedElementAdapter.java @@ -9,6 +9,7 @@ import org.openqa.selenium.WebElement; import utam.core.element.Element; +import utam.core.selenium.element.DriverAdapter; import utam.core.selenium.element.ElementAdapter; /** @@ -34,12 +35,16 @@ interface SmartUnwrapper { private final SmartUnwrapper smartUnwrapper; /** - * Constructs a WrappedElementAdapter + * Constructs a WrappedElementAdapter. + * + *
Accepts any {@link DriverAdapter} (e.g. {@link WrappedDriverAdapter} for web, {@link + * WrappedMobileDriverAdapter} for mobile hybrid) so the wrapped element/driver pattern applies + * uniformly across platforms. * * @param element the wrapped element decorator - * @param driver the wrapped driver adapter + * @param driver the driver adapter (typically a wrapped variant) */ - WrappedElementAdapter(WrappedElementDecorator element, WrappedDriverAdapter driver) { + WrappedElementAdapter(WrappedElementDecorator element, DriverAdapter driver) { super(element, driver); this.smartUnwrapper = () -> element.unwrap(); } @@ -58,7 +63,7 @@ protected WebElement unwrap() { protected Element wrapElement(WebElement element) { if (element instanceof WrappedElementDecorator) { return new WrappedElementAdapter( - (WrappedElementDecorator) element, (WrappedDriverAdapter) this.driverAdapter); + (WrappedElementDecorator) element, (DriverAdapter) this.driverAdapter); } return super.wrapElement(element); } diff --git a/utam-core/src/main/java/utam/core/selenium/wrapped/WrappedMobileDriverAdapter.java b/utam-core/src/main/java/utam/core/selenium/wrapped/WrappedMobileDriverAdapter.java new file mode 100644 index 00000000..20016e95 --- /dev/null +++ b/utam-core/src/main/java/utam/core/selenium/wrapped/WrappedMobileDriverAdapter.java @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2026, salesforce.com, inc. + * All rights reserved. + * SPDX-License-Identifier: MIT + * For full license text, see the LICENSE file in the repo root + * or https://opensource.org/licenses/MIT + */ +package utam.core.selenium.wrapped; + +import io.appium.java_client.AppiumDriver; +import org.openqa.selenium.WebDriver; +import org.openqa.selenium.WebElement; +import utam.core.driver.DriverConfig; +import utam.core.element.Element; +import utam.core.selenium.appium.MobileDriverAdapter; + +/** + * WrappedMobileDriverAdapter wraps a {@link WrappedDriverDecorator} whose underlying driver is an + * {@link AppiumDriver}. + * + *
This exists because mobile hybrid tests need both: + * + *
Routing strategy: + * + *
Returns the underlying {@link AppiumDriver} (not {@code getSeleniumDriver()}, which is the + * wrapped driver). All Appium-only APIs in {@link MobileDriverAdapter} (context switching, {@code + * mobile: getContexts}, etc.) flow through this method, so they operate on the real driver. + */ + @Override + protected AppiumDriver getAppiumDriver() { + return appiumDriver; + } + + /** + * Wrap a WebElement into an Element. If the element is a {@link WrappedElementDecorator}, creates + * a {@link WrappedElementAdapter} with smart finding capabilities. + */ + @Override + protected Element wrapElement(WebElement element) { + if (element instanceof WrappedElementDecorator) { + return new WrappedElementAdapter((WrappedElementDecorator) element, this); + } + return super.wrapElement(element); + } +}