Skip to content
Merged
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
19 changes: 16 additions & 3 deletions src/main/java/io/appium/java_client/AppiumDriver.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,11 @@
import static com.google.common.base.Preconditions.checkNotNull;
import static io.appium.java_client.remote.MobileCapabilityType.PLATFORM_NAME;
import static org.apache.commons.lang3.StringUtils.containsIgnoreCase;
import static org.apache.commons.lang3.StringUtils.isBlank;

import com.google.common.collect.ImmutableMap;

import io.appium.java_client.internal.CapabilityHelpers;
import io.appium.java_client.internal.JsonToMobileElementConverter;
import io.appium.java_client.remote.AppiumCommandExecutor;
import io.appium.java_client.remote.MobileCapabilityType;
Expand Down Expand Up @@ -88,7 +90,7 @@ public AppiumDriver(HttpCommandExecutor executor, Capabilities capabilities) {
locationContext = new RemoteLocationContext(executeMethod);
super.setErrorHandler(errorHandler);
this.remoteAddress = executor.getAddressOfRemoteServer();
this.setElementConverter(new JsonToMobileElementConverter(this, this));
this.setElementConverter(new JsonToMobileElementConverter(this));
}

public AppiumDriver(URL remoteAddress, Capabilities desiredCapabilities) {
Expand Down Expand Up @@ -314,8 +316,19 @@ public URL getRemoteAddress() {

@Override
public boolean isBrowser() {
return super.isBrowser()
&& !containsIgnoreCase(getContext(), "NATIVE_APP");
String browserName = CapabilityHelpers.getCapability(getCapabilities(), "browserName", String.class);
if (!isBlank(browserName)) {
try {
return (boolean) executeScript("return !!window.navigator;");
} catch (WebDriverException ign) {
// ignore
}
}
try {
return !containsIgnoreCase(getContext(), "NATIVE_APP");
} catch (WebDriverException e) {
return false;
}
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* See the NOTICE file distributed with this work for additional
* information regarding copyright ownership.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package io.appium.java_client.internal;

import org.openqa.selenium.Capabilities;

import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.List;

public class CapabilityHelpers {
public static final String APPIUM_PREFIX = "appium:";

/**
* Helper that is used for capability values retrieval.
* Supports both prefixed W3C and "classic" capability names.
*
* @param caps driver caps object
* @param name capability name
* @param expectedType the expected capability type
* @return The retrieved capability value or null if the cap either not present has an unexpected type
*/
@Nullable
public static <T> T getCapability(Capabilities caps, String name, Class<T> expectedType) {
List<String> possibleNames = new ArrayList<>();
possibleNames.add(name);
if (!name.startsWith(APPIUM_PREFIX)) {
possibleNames.add(APPIUM_PREFIX + name);
}
for (String capName : possibleNames) {
if (caps.getCapability(capName) != null
&& expectedType.isAssignableFrom(caps.getCapability(capName).getClass())) {
return expectedType.cast(caps.getCapability(capName));
}
}
return null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -50,12 +50,10 @@ public enum ElementMap {
mobileElementMap = builder.build();
}



private final String platformOrAutomation;
private final Class<? extends RemoteWebElement> elementClass;

private ElementMap(String platformOrAutomation, Class<? extends MobileElement> elementClass) {
ElementMap(String platformOrAutomation, Class<? extends MobileElement> elementClass) {
this.platformOrAutomation = platformOrAutomation;
this.elementClass = elementClass;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@

import static io.appium.java_client.internal.ElementMap.getElementClass;

import io.appium.java_client.HasSessionDetails;
import org.openqa.selenium.Capabilities;
import org.openqa.selenium.WebDriverException;
import org.openqa.selenium.remote.RemoteWebDriver;
import org.openqa.selenium.remote.RemoteWebElement;
Expand All @@ -41,20 +41,20 @@ public class JsonToMobileElementConverter extends JsonToWebElementConverter {
* Creates a new instance based on {@code driver} and object with session details.
*
* @param driver an instance of {@link RemoteWebDriver} subclass
* @param hasSessionDetails object that has session details
*/
public JsonToMobileElementConverter(RemoteWebDriver driver, HasSessionDetails hasSessionDetails) {
public JsonToMobileElementConverter(RemoteWebDriver driver) {
super(driver);
this.driver = driver;
this.platform = hasSessionDetails.getPlatformName();
this.automation = hasSessionDetails.getAutomationName();
Capabilities caps = driver.getCapabilities();
this.platform = CapabilityHelpers.getCapability(caps, "platformName", String.class);
this.automation = CapabilityHelpers.getCapability(caps, "automationName", String.class);
}

@Override
public Object apply(Object result) {
Object toBeReturned = result;
if (toBeReturned instanceof RemoteWebElement) {
toBeReturned = newRemoteWebElement();
toBeReturned = newRemoteWebElement();
((RemoteWebElement) toBeReturned).setId(((RemoteWebElement) result).getId());
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,20 +20,22 @@
import static io.appium.java_client.pagefactory.utils.ProxyFactory.getEnhancedProxy;
import static io.appium.java_client.pagefactory.utils.WebDriverUnpackUtility.unpackWebDriverFromSearchContext;
import static java.time.Duration.ofSeconds;
import static java.util.Optional.ofNullable;

import com.google.common.collect.ImmutableList;

import io.appium.java_client.HasSessionDetails;
import io.appium.java_client.MobileElement;
import io.appium.java_client.android.AndroidElement;
import io.appium.java_client.internal.CapabilityHelpers;
import io.appium.java_client.ios.IOSElement;
import io.appium.java_client.pagefactory.bys.ContentType;
import io.appium.java_client.pagefactory.locator.CacheableLocator;
import io.appium.java_client.windows.WindowsElement;
import org.openqa.selenium.Capabilities;
import org.openqa.selenium.HasCapabilities;
import org.openqa.selenium.SearchContext;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.remote.RemoteWebDriver;
import org.openqa.selenium.remote.RemoteWebElement;
import org.openqa.selenium.support.pagefactory.DefaultFieldDecorator;
import org.openqa.selenium.support.pagefactory.ElementLocator;
Expand Down Expand Up @@ -71,30 +73,24 @@ public class AppiumFieldDecorator implements FieldDecorator {
private final String automation;
private final Duration duration;


/**
* Creates field decorator based on {@link SearchContext} and timeout {@code duration}.
*
* @param context is an instance of {@link SearchContext}
* It may be the instance of {@link WebDriver} or {@link WebElement} or
* {@link Widget} or some other user's extension/implementation.
* @param context is an instance of {@link SearchContext}
* It may be the instance of {@link WebDriver} or {@link WebElement} or
* {@link Widget} or some other user's extension/implementation.
* @param duration is a desired duration of the waiting for an element presence.
*/
public AppiumFieldDecorator(SearchContext context, Duration duration) {
this.webDriver = unpackWebDriverFromSearchContext(context);
HasSessionDetails hasSessionDetails = ofNullable(this.webDriver).map(webDriver -> {
if (!HasSessionDetails.class.isAssignableFrom(webDriver.getClass())) {
return null;
}
return HasSessionDetails.class.cast(webDriver);
}).orElse(null);

if (hasSessionDetails == null) {
platform = null;
automation = null;
if (this.webDriver instanceof HasCapabilities) {
Capabilities caps = ((HasCapabilities) this.webDriver).getCapabilities();
this.platform = CapabilityHelpers.getCapability(caps, "platformName", String.class);
this.automation = CapabilityHelpers.getCapability(caps, "automationName", String.class);
} else {
platform = hasSessionDetails.getPlatformName();
automation = hasSessionDetails.getAutomationName();
this.platform = null;
this.automation = null;
}

this.duration = duration;
Expand All @@ -115,7 +111,8 @@ protected List<WebElement> proxyForListLocator(ClassLoader ignored,
return getEnhancedProxy(ArrayList.class, elementInterceptor);
}

@Override protected boolean isDecoratableList(Field field) {
@Override
protected boolean isDecoratableList(Field field) {
if (!List.class.isAssignableFrom(field.getType())) {
return false;
}
Expand Down Expand Up @@ -148,7 +145,7 @@ public AppiumFieldDecorator(SearchContext context) {
* Decorated page object {@code field}.
*
* @param ignored class loader is ignored by current implementation
* @param field is {@link Field} of page object which is supposed to be decorated.
* @param field is {@link Field} of page object which is supposed to be decorated.
* @return a field value or null.
*/
public Object decorate(ClassLoader ignored, Field field) {
Expand Down Expand Up @@ -197,19 +194,19 @@ private Object decorateWidget(Field field) {

CacheableLocator locator = widgetLocatorFactory.createLocator(field);
Map<ContentType, Constructor<? extends Widget>> map =
OverrideWidgetReader.read(widgetType, field, platform);
OverrideWidgetReader.read(widgetType, field, platform);

if (isAlist) {
return getEnhancedProxy(ArrayList.class,
new WidgetListInterceptor(locator, webDriver, map, widgetType,
duration));
new WidgetListInterceptor(locator, webDriver, map, widgetType,
duration));
}

Constructor<? extends Widget> constructor =
WidgetConstructorUtil.findConvenientConstructor(widgetType);
return getEnhancedProxy(widgetType, new Class[] {constructor.getParameterTypes()[0]},
new Object[] {proxyForAnElement(locator)},
new WidgetInterceptor(locator, webDriver, null, map, duration));
WidgetConstructorUtil.findConvenientConstructor(widgetType);
return getEnhancedProxy(widgetType, new Class[]{constructor.getParameterTypes()[0]},
new Object[]{proxyForAnElement(locator)},
new WidgetInterceptor(locator, webDriver, null, map, duration));
}

private WebElement proxyForAnElement(ElementLocator locator) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,15 +82,15 @@ public static WebDriver unpackWebDriverFromSearchContext(SearchContext searchCon
public static ContentType getCurrentContentType(SearchContext context) {
return ofNullable(unpackWebDriverFromSearchContext(context)).map(driver -> {
if (HasSessionDetails.class.isAssignableFrom(driver.getClass())) {
HasSessionDetails hasSessionDetails = HasSessionDetails.class.cast(driver);
HasSessionDetails hasSessionDetails = (HasSessionDetails) driver;

if (!hasSessionDetails.isBrowser()) {
return NATIVE_MOBILE_SPECIFIC;
}
}

if (ContextAware.class.isAssignableFrom(driver.getClass())) { //it is desktop browser
ContextAware contextAware = ContextAware.class.cast(driver);
ContextAware contextAware = (ContextAware) driver;
String currentContext = contextAware.getContext();
if (containsIgnoreCase(currentContext, NATIVE_APP_PATTERN)) {
return NATIVE_MOBILE_SPECIFIC;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

import static com.google.common.collect.ImmutableMap.of;
import static com.google.common.collect.ImmutableMap.toImmutableMap;
import static io.appium.java_client.internal.CapabilityHelpers.APPIUM_PREFIX;
import static io.appium.java_client.remote.MobileCapabilityType.FORCE_MJSONWP;
import static java.nio.charset.StandardCharsets.UTF_8;
import static java.util.Optional.ofNullable;
Expand Down Expand Up @@ -88,7 +89,6 @@ public class NewAppiumSessionPayload implements Closeable {
.addAll(getAppiumCapabilities(AndroidMobileCapabilityType.class))
.addAll(getAppiumCapabilities(IOSMobileCapabilityType.class))
.addAll(getAppiumCapabilities(YouiEngineCapabilityType.class)).build();
private static final String APPIUM_PREFIX = "appium:";
private static final String DESIRED_CAPABILITIES = "desiredCapabilities";
private static final String CAPABILITIES = "capabilities";
private static final String REQUIRED_CAPABILITIES = "requiredCapabilities";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
public class IOSNativeWebTapSettingTest extends BaseSafariTest {

@Test public void nativeWebTapSettingTest() {
assertTrue(driver.isBrowser());
driver.get("https://saucelabs.com/test/guinea-pig");

// do a click with nativeWebTap turned on, and assert we get to the right page
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,26 +3,30 @@
import static com.google.common.collect.ImmutableList.of;
import static io.appium.java_client.remote.AutomationName.APPIUM;
import static io.appium.java_client.remote.AutomationName.IOS_XCUI_TEST;
import static io.appium.java_client.remote.AutomationName.SELENDROID;
import static io.appium.java_client.remote.MobilePlatform.ANDROID;
import static io.appium.java_client.remote.MobilePlatform.IOS;
import static io.appium.java_client.remote.MobilePlatform.WINDOWS;
import static org.apache.commons.lang3.StringUtils.EMPTY;

import io.appium.java_client.HasSessionDetails;
import org.openqa.selenium.By;
import org.openqa.selenium.Capabilities;
import org.openqa.selenium.Cookie;
import org.openqa.selenium.HasCapabilities;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.logging.Logs;
import org.openqa.selenium.remote.DesiredCapabilities;
import org.openqa.selenium.remote.Response;

import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;

public abstract class AbstractStubWebDriver implements WebDriver, HasSessionDetails {
public abstract class AbstractStubWebDriver implements WebDriver, HasSessionDetails,
HasCapabilities {
@Override
public Response execute(String driverCommand, Map<String, ?> parameters) {
return null;
Expand Down Expand Up @@ -104,6 +108,14 @@ public Navigation navigate() {
return null;
}

@Override
public Capabilities getCapabilities() {
Map<String, Object> caps = new HashMap<>();
caps.put("platformName", getPlatformName());
caps.put("automationName", getAutomationName());
return new DesiredCapabilities(caps);
}

@Override
public Options manage() {
return new Options() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ protected ExtendedWidgetTest(ExtendedApp app, WebDriver driver) {
public abstract void checkCaseWhenWidgetClassHasNoDeclaredAnnotationButItHasSuperclass();

@Test
public abstract void checkCaseWhenBothWidgetFieldAndClassHaveDelaredAnnotations();
public abstract void checkCaseWhenBothWidgetFieldAndClassHaveDeclaredAnnotations();

protected static void checkThatLocatorsAreCreatedCorrectly(DefaultStubWidget single,
List<DefaultStubWidget> multiple, By rootLocator,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ public void checkCaseWhenWidgetClassHasNoDeclaredAnnotationButItHasSuperclass()
}

@Override
public void checkCaseWhenBothWidgetFieldAndClassHaveDelaredAnnotations() {
public void checkCaseWhenBothWidgetFieldAndClassHaveDeclaredAnnotations() {
checkThatLocatorsAreCreatedCorrectly(((ExtendedApp) app).getExtendedWidgetWithOverriddenLocators(),
((ExtendedApp) app).getExtendedWidgetsWithOverriddenLocators(),
AndroidUIAutomator(ANDROID_EXTERNALLY_DEFINED_WIDGET_LOCATOR),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ public void checkCaseWhenWidgetClassHasNoDeclaredAnnotationButItHasSuperclass()
}

@Override
public void checkCaseWhenBothWidgetFieldAndClassHaveDelaredAnnotations() {
public void checkCaseWhenBothWidgetFieldAndClassHaveDeclaredAnnotations() {
checkThatLocatorsAreCreatedCorrectly(((ExtendedApp) app).getExtendedWidgetWithOverriddenLocators(),
((ExtendedApp) app).getExtendedWidgetsWithOverriddenLocators(),
iOSNsPredicateString(XCUIT_EXTERNALLY_DEFINED_WIDGET_LOCATOR),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ public void checkCaseWhenWidgetClassHasNoDeclaredAnnotationButItHasSuperclass()
}

@Override
public void checkCaseWhenBothWidgetFieldAndClassHaveDelaredAnnotations() {
public void checkCaseWhenBothWidgetFieldAndClassHaveDeclaredAnnotations() {
checkThatLocatorsAreCreatedCorrectly(((ExtendedApp) app).getExtendedWidgetWithOverriddenLocators(),
((ExtendedApp) app).getExtendedWidgetsWithOverriddenLocators(),
windowsAutomation(WINDOWS_EXTERNALLY_DEFINED_WIDGET_LOCATOR),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

import static io.github.bonigarcia.wdm.WebDriverManager.chromedriver;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
Expand Down Expand Up @@ -197,6 +198,7 @@ public class StartingAppLocallyTest {
assertTrue(caps.getCapability(MobileCapabilityType.PLATFORM_NAME)
.toString().equalsIgnoreCase(MobilePlatform.IOS));
assertNotEquals(null, caps.getCapability(MobileCapabilityType.DEVICE_NAME));
assertFalse(driver.isBrowser());
} finally {
driver.quit();
}
Expand Down