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; + } + } +}