Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -244,7 +244,7 @@ public boolean isNativeContext() {
return MobileDriverUtils.isNative(getAppiumDriver());
}

final AppiumDriver getAppiumDriver() {
protected AppiumDriver getAppiumDriver() {
return (AppiumDriver) getSeleniumDriver();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

/**
Expand All @@ -34,12 +35,16 @@ interface SmartUnwrapper {
private final SmartUnwrapper smartUnwrapper;

/**
* Constructs a WrappedElementAdapter
* Constructs a WrappedElementAdapter.
*
* <p>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();
}
Expand All @@ -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);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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}.
*
* <p>This exists because mobile hybrid tests need both:
*
* <ul>
* <li>WebView context switching ({@code mobile: getContexts}, {@code SupportsContextSwitching}),
* which only {@link MobileDriverAdapter} performs.
* <li>Custom {@code findElement}/{@code findElements} routing through the wrapper (e.g. for
* smart-finder libraries that intercept element lookups).
* </ul>
*
* <p>Routing strategy:
*
* <ul>
* <li>{@link DriverAdapter#getSeleniumDriver()} returns the wrapped driver, so {@code
* findElement} / {@code findElements} flow through it.
* <li>{@link MobileDriverAdapter#getAppiumDriver()} is overridden to return the unwrapped {@link
* AppiumDriver}, so context switching, {@code mobile: getContexts}, and other Appium-only
* APIs operate on the real driver.
* </ul>
*
* @author elizaveta.ivanova
* @since 264
*/
public class WrappedMobileDriverAdapter extends MobileDriverAdapter {

private final WrappedDriverDecorator wrappedDriver;
private final AppiumDriver appiumDriver;

/**
* Constructs a WrappedMobileDriverAdapter.
*
* @param wrappedDriver the wrapped driver decorator (used as the Selenium driver for {@code
* findElement} routing)
* @param appiumDriver the underlying AppiumDriver (used for context switching and other
* Appium-only APIs)
* @param driverConfig driver configuration
*/
public WrappedMobileDriverAdapter(
WrappedDriverDecorator wrappedDriver, AppiumDriver appiumDriver, DriverConfig driverConfig) {
super(appiumDriver, driverConfig);
this.wrappedDriver = wrappedDriver;
this.appiumDriver = appiumDriver;
// After super-ctor, the inherited Selenium driver field is the AppiumDriver. Replace it with
// the wrappedDriver so findElement/findElements (resolved on the inherited driver) flow
// through the wrapper. getAppiumDriver() below keeps context switching pointed at the real
// AppiumDriver.
resetDriver(wrappedDriver);
}

/** {@inheritDoc} */
@Override
protected WebDriver unwrap() {
return wrappedDriver.unwrap();
}

/**
* {@inheritDoc}
*
* <p>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);
}
}