diff --git a/src/main/java/io/appium/java_client/internal/JsonToMobileElementConverter.java b/src/main/java/io/appium/java_client/internal/JsonToMobileElementConverter.java index 180470773..d78b90c7d 100644 --- a/src/main/java/io/appium/java_client/internal/JsonToMobileElementConverter.java +++ b/src/main/java/io/appium/java_client/internal/JsonToMobileElementConverter.java @@ -18,9 +18,6 @@ import static io.appium.java_client.internal.ElementMap.getElementClass; -import com.google.common.collect.Iterables; -import com.google.common.collect.Lists; - import io.appium.java_client.HasSessionDetails; import org.openqa.selenium.WebDriverException; import org.openqa.selenium.remote.RemoteWebDriver; @@ -28,7 +25,6 @@ import org.openqa.selenium.remote.internal.JsonToWebElementConverter; import java.lang.reflect.Constructor; -import java.util.Collection; /** * Reconstitutes {@link org.openqa.selenium.WebElement}s from their JSON representation. Will recursively convert Lists @@ -54,45 +50,30 @@ public JsonToMobileElementConverter(RemoteWebDriver driver, HasSessionDetails ha this.automation = hasSessionDetails.getAutomationName(); } - /** - * This method converts a command result. - * - * @param result is the result of a command execution. - * @return the result - */ + @Override public Object apply(Object result) { - if (result instanceof Collection) { - Collection results = (Collection) result; - return Lists.newArrayList(Iterables.transform(results, this)); - } - - if (result instanceof RemoteWebElement) { - RemoteWebElement resultElement = RemoteWebElement.class.cast(result); - RemoteWebElement element = newMobileElement(); - element.setParent(driver); - element.setId(resultElement.getId()); - element.setFileDetector(driver.getFileDetector()); - return element; + Object toBeReturned = result; + if (toBeReturned instanceof RemoteWebElement) { + toBeReturned = newRemoteWebElement(); + ((RemoteWebElement) toBeReturned).setId(((RemoteWebElement) result).getId()); } - if (result instanceof Number) { - if (result instanceof Float || result instanceof Double) { - return ((Number) result).doubleValue(); - } - return ((Number) result).longValue(); - } - - return result; + return super.apply(toBeReturned); } - private RemoteWebElement newMobileElement() { + @Override + protected RemoteWebElement newRemoteWebElement() { Class target; target = getElementClass(platform, automation); + try { Constructor constructor = target.getDeclaredConstructor(); constructor.setAccessible(true); RemoteWebElement result = constructor.newInstance(); + result.setParent(driver); + result.setFileDetector(driver.getFileDetector()); + return result; } catch (Exception e) { throw new WebDriverException(e); diff --git a/src/main/java/io/appium/java_client/remote/AppiumCommandExecutor.java b/src/main/java/io/appium/java_client/remote/AppiumCommandExecutor.java index b62e99e01..f8651dad6 100644 --- a/src/main/java/io/appium/java_client/remote/AppiumCommandExecutor.java +++ b/src/main/java/io/appium/java_client/remote/AppiumCommandExecutor.java @@ -25,15 +25,19 @@ import org.openqa.selenium.WebDriverException; import org.openqa.selenium.remote.Command; +import org.openqa.selenium.remote.CommandCodec; import org.openqa.selenium.remote.CommandInfo; import org.openqa.selenium.remote.DriverCommand; import org.openqa.selenium.remote.HttpCommandExecutor; import org.openqa.selenium.remote.Response; import org.openqa.selenium.remote.http.HttpClient; +import org.openqa.selenium.remote.http.HttpRequest; +import org.openqa.selenium.remote.http.W3CHttpCommandCodec; import org.openqa.selenium.remote.internal.ApacheHttpClient; import org.openqa.selenium.remote.service.DriverService; import java.io.IOException; +import java.lang.reflect.Field; import java.net.ConnectException; import java.net.URL; import java.util.Map; @@ -74,7 +78,42 @@ public AppiumCommandExecutor(Map additionalCommands, this(additionalCommands, service, new ApacheHttpClient.Factory()); } - @Override public Response execute(Command command) throws WebDriverException { + private B getPrivateFieldValue(String fieldName, Class fieldType) { + try { + final Field f = getClass().getSuperclass().getDeclaredField(fieldName); + f.setAccessible(true); + return fieldType.cast(f.get(this)); + } catch (NoSuchFieldException | IllegalAccessException e) { + throw new WebDriverException(e); + } + } + + private void setPrivateFieldValue(String fieldName, Object newValue) { + try { + final Field f = getClass().getSuperclass().getDeclaredField(fieldName); + f.setAccessible(true); + f.set(this, newValue); + } catch (NoSuchFieldException | IllegalAccessException e) { + throw new WebDriverException(e); + } + } + + private Map getAdditionalCommands() { + //noinspection unchecked + return getPrivateFieldValue("additionalCommands", Map.class); + } + + private CommandCodec getCommandCodec() { + //noinspection unchecked + return getPrivateFieldValue("commandCodec", CommandCodec.class); + } + + private void setCommandCodec(CommandCodec newCodec) { + setPrivateFieldValue("commandCodec", newCodec); + } + + @Override + public Response execute(Command command) throws WebDriverException { if (DriverCommand.NEW_SESSION.equals(command.getName())) { serviceOptional.ifPresent(driverService -> { try { @@ -85,8 +124,9 @@ public AppiumCommandExecutor(Map additionalCommands, }); } + Response response; try { - return super.execute(command); + response = super.execute(command); } catch (Throwable t) { Throwable rootCause = Throwables.getRootCause(t); if (rootCause instanceof ConnectException @@ -107,5 +147,13 @@ public AppiumCommandExecutor(Map additionalCommands, serviceOptional.ifPresent(DriverService::stop); } } + + if (DriverCommand.NEW_SESSION.equals(command.getName()) + && getCommandCodec() instanceof W3CHttpCommandCodec) { + setCommandCodec(new AppiumW3CHttpCommandCodec()); + getAdditionalCommands().forEach(this::defineCommand); + } + + return response; } } \ No newline at end of file diff --git a/src/main/java/io/appium/java_client/remote/AppiumW3CHttpCommandCodec.java b/src/main/java/io/appium/java_client/remote/AppiumW3CHttpCommandCodec.java new file mode 100644 index 000000000..61df1ff47 --- /dev/null +++ b/src/main/java/io/appium/java_client/remote/AppiumW3CHttpCommandCodec.java @@ -0,0 +1,81 @@ +/* + * 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.remote; + +import org.openqa.selenium.remote.http.W3CHttpCommandCodec; + +import java.util.Map; + +import static org.openqa.selenium.remote.DriverCommand.GET_ELEMENT_ATTRIBUTE; +import static org.openqa.selenium.remote.DriverCommand.GET_ELEMENT_LOCATION; +import static org.openqa.selenium.remote.DriverCommand.GET_ELEMENT_LOCATION_ONCE_SCROLLED_INTO_VIEW; +import static org.openqa.selenium.remote.DriverCommand.GET_ELEMENT_SIZE; +import static org.openqa.selenium.remote.DriverCommand.GET_PAGE_SOURCE; +import static org.openqa.selenium.remote.DriverCommand.IS_ELEMENT_DISPLAYED; +import static org.openqa.selenium.remote.DriverCommand.SEND_KEYS_TO_ACTIVE_ELEMENT; +import static org.openqa.selenium.remote.DriverCommand.SEND_KEYS_TO_ELEMENT; +import static org.openqa.selenium.remote.DriverCommand.SET_ALERT_VALUE; +import static org.openqa.selenium.remote.DriverCommand.SET_TIMEOUT; +import static org.openqa.selenium.remote.DriverCommand.SUBMIT_ELEMENT; + + +public class AppiumW3CHttpCommandCodec extends W3CHttpCommandCodec { + + /** + * This class overrides the built-in Selenium W3C commands codec, + * since the latter hardcodes many commands in Javascript, + * which does not work with Appium. + * Check https://www.w3.org/TR/webdriver/ to see all available W3C + * endpoints. + */ + public AppiumW3CHttpCommandCodec() { + defineCommand(GET_ELEMENT_ATTRIBUTE, get("/session/:sessionId/element/:id/attribute/:name")); + defineCommand(GET_PAGE_SOURCE, get("/session/:sessionId/source")); + } + + @Override + public void alias(String commandName, String isAnAliasFor) { + // This blocks parent constructor from undesirable aliases assigning + switch (commandName) { + case GET_ELEMENT_ATTRIBUTE: + case GET_ELEMENT_LOCATION: + case GET_ELEMENT_LOCATION_ONCE_SCROLLED_INTO_VIEW: + case GET_ELEMENT_SIZE: + case IS_ELEMENT_DISPLAYED: + case SUBMIT_ELEMENT: + case GET_PAGE_SOURCE: + return; + default: + super.alias(commandName, isAnAliasFor); + break; + } + } + + @Override + protected Map amendParameters(String name, Map parameters) { + // This blocks parent constructor from undesirable parameters amending + switch (name) { + case SEND_KEYS_TO_ACTIVE_ELEMENT: + case SEND_KEYS_TO_ELEMENT: + case SET_ALERT_VALUE: + case SET_TIMEOUT: + return super.amendParameters(name, parameters); + default: + return parameters; + } + } +}