diff --git a/src/main/java/io/appium/java_client/AppiumDriver.java b/src/main/java/io/appium/java_client/AppiumDriver.java index 54bf7708e..0a51b5271 100644 --- a/src/main/java/io/appium/java_client/AppiumDriver.java +++ b/src/main/java/io/appium/java_client/AppiumDriver.java @@ -42,6 +42,7 @@ import org.openqa.selenium.remote.DriverCommand; import org.openqa.selenium.remote.ErrorHandler; import org.openqa.selenium.remote.ExecuteMethod; +import org.openqa.selenium.remote.HttpCommandExecutor; import org.openqa.selenium.remote.Response; import org.openqa.selenium.remote.html5.RemoteLocationContext; import org.openqa.selenium.remote.http.HttpClient; @@ -82,7 +83,7 @@ public class AppiumDriver * @param capabilities take a look * at {@link org.openqa.selenium.Capabilities} */ - public AppiumDriver(AppiumCommandExecutor executor, Capabilities capabilities) { + public AppiumDriver(HttpCommandExecutor executor, Capabilities capabilities) { super(executor, capabilities); this.executeMethod = new AppiumExecutionMethod(this); locationContext = new RemoteLocationContext(executeMethod); diff --git a/src/main/java/io/appium/java_client/MobileCommand.java b/src/main/java/io/appium/java_client/MobileCommand.java index 40a131a0e..7ab77cc90 100644 --- a/src/main/java/io/appium/java_client/MobileCommand.java +++ b/src/main/java/io/appium/java_client/MobileCommand.java @@ -19,6 +19,7 @@ import com.google.common.collect.ImmutableMap; import org.apache.commons.lang3.StringUtils; +import org.openqa.selenium.remote.CommandInfo; import org.openqa.selenium.remote.http.HttpMethod; import java.time.Duration; @@ -79,7 +80,7 @@ public class MobileCommand { protected static final String SET_SETTINGS; protected static final String GET_CURRENT_PACKAGE; - public static final Map commandRepository; + public static final Map commandRepository; static { RESET = "reset"; diff --git a/src/main/java/io/appium/java_client/android/AndroidDriver.java b/src/main/java/io/appium/java_client/android/AndroidDriver.java index 0bfe0974e..fc29f9ecb 100644 --- a/src/main/java/io/appium/java_client/android/AndroidDriver.java +++ b/src/main/java/io/appium/java_client/android/AndroidDriver.java @@ -24,12 +24,12 @@ import io.appium.java_client.CommandExecutionHelper; import io.appium.java_client.FindsByAndroidUIAutomator; import io.appium.java_client.PressesKeyCode; -import io.appium.java_client.remote.AppiumCommandExecutor; import io.appium.java_client.remote.MobilePlatform; import io.appium.java_client.service.local.AppiumDriverLocalService; import io.appium.java_client.service.local.AppiumServiceBuilder; import org.openqa.selenium.Capabilities; import org.openqa.selenium.WebElement; +import org.openqa.selenium.remote.HttpCommandExecutor; import org.openqa.selenium.remote.http.HttpClient; import java.net.URL; @@ -58,7 +58,7 @@ public class AndroidDriver * @param capabilities take a look * at {@link org.openqa.selenium.Capabilities} */ - public AndroidDriver(AppiumCommandExecutor executor, Capabilities capabilities) { + public AndroidDriver(HttpCommandExecutor executor, Capabilities capabilities) { super(executor, substituteMobilePlatform(capabilities, ANDROID_PLATFORM)); } diff --git a/src/main/java/io/appium/java_client/ios/IOSDriver.java b/src/main/java/io/appium/java_client/ios/IOSDriver.java index 0394264d8..995c6370c 100644 --- a/src/main/java/io/appium/java_client/ios/IOSDriver.java +++ b/src/main/java/io/appium/java_client/ios/IOSDriver.java @@ -24,7 +24,6 @@ import io.appium.java_client.FindsByIosNSPredicate; import io.appium.java_client.FindsByIosUIAutomation; import io.appium.java_client.HidesKeyboardWithKeyName; -import io.appium.java_client.remote.AppiumCommandExecutor; import io.appium.java_client.remote.MobilePlatform; import io.appium.java_client.service.local.AppiumDriverLocalService; import io.appium.java_client.service.local.AppiumServiceBuilder; @@ -32,6 +31,7 @@ import org.openqa.selenium.Capabilities; import org.openqa.selenium.WebElement; 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.security.Credentials; @@ -64,7 +64,7 @@ public class IOSDriver * @param capabilities take a look * at {@link org.openqa.selenium.Capabilities} */ - public IOSDriver(AppiumCommandExecutor executor, Capabilities capabilities) { + public IOSDriver(HttpCommandExecutor executor, Capabilities capabilities) { super(executor, substituteMobilePlatform(capabilities, IOS_PLATFORM)); } 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 18a928fa8..b62e99e01 100644 --- a/src/main/java/io/appium/java_client/remote/AppiumCommandExecutor.java +++ b/src/main/java/io/appium/java_client/remote/AppiumCommandExecutor.java @@ -17,28 +17,19 @@ package io.appium.java_client.remote; import static com.google.common.base.Preconditions.checkNotNull; -import static com.google.common.base.Throwables.getRootCause; import static com.google.common.base.Throwables.throwIfUnchecked; -import static org.openqa.selenium.remote.DriverCommand.GET_ALL_SESSIONS; -import static org.openqa.selenium.remote.DriverCommand.NEW_SESSION; -import static org.openqa.selenium.remote.DriverCommand.QUIT; - -import io.appium.java_client.AppiumCommandInfo; -import org.openqa.selenium.NoSuchSessionException; -import org.openqa.selenium.SessionNotCreatedException; -import org.openqa.selenium.UnsupportedCommandException; +import static java.util.Optional.ofNullable; + +import com.google.common.base.Supplier; +import com.google.common.base.Throwables; + import org.openqa.selenium.WebDriverException; import org.openqa.selenium.remote.Command; -import org.openqa.selenium.remote.CommandCodec; -import org.openqa.selenium.remote.CommandExecutor; -import org.openqa.selenium.remote.Dialect; +import org.openqa.selenium.remote.CommandInfo; import org.openqa.selenium.remote.DriverCommand; -import org.openqa.selenium.remote.HttpSessionId; +import org.openqa.selenium.remote.HttpCommandExecutor; import org.openqa.selenium.remote.Response; -import org.openqa.selenium.remote.ResponseCodec; import org.openqa.selenium.remote.http.HttpClient; -import org.openqa.selenium.remote.http.HttpRequest; -import org.openqa.selenium.remote.http.HttpResponse; import org.openqa.selenium.remote.internal.ApacheHttpClient; import org.openqa.selenium.remote.service.DriverService; @@ -46,140 +37,75 @@ import java.net.ConnectException; import java.net.URL; import java.util.Map; +import java.util.Optional; -public class AppiumCommandExecutor implements CommandExecutor { - - private final URL remoteServer; - private final HttpClient client; - private final Map additionalCommands; - private CommandCodec commandCodec; - private ResponseCodec responseCodec; - private DriverService service; - - /** - * Cretes an instance that sends requests and receives responses. - * - * @param additionalCommands is the mapped command repository - * @param addressOfRemoteServer is the url to connect to the Appium remote/local server - * @param httpClientFactory is the http client factory - */ - public AppiumCommandExecutor(Map additionalCommands, - URL addressOfRemoteServer, HttpClient.Factory httpClientFactory) { - checkNotNull(addressOfRemoteServer); - remoteServer = addressOfRemoteServer; - this.additionalCommands = additionalCommands; - this.client = httpClientFactory.createClient(remoteServer); - } +public class AppiumCommandExecutor extends HttpCommandExecutor { - public AppiumCommandExecutor(Map additionalCommands, DriverService service, - HttpClient.Factory httpClientFactory) { - this(additionalCommands, service.getUrl(), httpClientFactory); - this.service = service; - } + private final Optional serviceOptional; - public AppiumCommandExecutor(Map additionalCommands, - URL addressOfRemoteServer) { - this(additionalCommands, addressOfRemoteServer, new ApacheHttpClient.Factory()); + private AppiumCommandExecutor(Map additionalCommands, DriverService service, + URL addressOfRemoteServer, + HttpClient.Factory httpClientFactory) { + super(additionalCommands, + ofNullable(service) + .map(DriverService::getUrl) + .orElse(addressOfRemoteServer), httpClientFactory); + serviceOptional = ofNullable(service); } - public AppiumCommandExecutor(Map additionalCommands, - DriverService service) { - this(additionalCommands, service, new ApacheHttpClient.Factory()); + public AppiumCommandExecutor(Map additionalCommands, DriverService service, + HttpClient.Factory httpClientFactory) { + this(additionalCommands, checkNotNull(service), null, httpClientFactory); } - public URL getAddressOfRemoteServer() { - return remoteServer; + public AppiumCommandExecutor(Map additionalCommands, + URL addressOfRemoteServer, HttpClient.Factory httpClientFactory) { + this(additionalCommands, null, checkNotNull(addressOfRemoteServer), httpClientFactory); } - private Response doExecute(Command command) throws IOException, WebDriverException { - if (command.getSessionId() == null) { - if (QUIT.equals(command.getName())) { - return new Response(); - } - if (!GET_ALL_SESSIONS.equals(command.getName()) - && !NEW_SESSION.equals(command.getName())) { - throw new NoSuchSessionException( - "Session ID is null. Using WebDriver after calling quit()?"); - } - } - - if (NEW_SESSION.equals(command.getName())) { - if (commandCodec != null) { - throw new SessionNotCreatedException("Session already exists"); - } - AppiumProtocolHandShake handshake = new AppiumProtocolHandShake(); - AppiumProtocolHandShake.Result result = handshake.createSession(client, command); - Dialect dialect = result.getDialect(); - commandCodec = dialect.getCommandCodec(); - - additionalCommands.forEach((key, value) -> { - checkNotNull(key); - checkNotNull(value); - commandCodec.defineCommand(key, value.getMethod(), value.getUrl()); - } ); - - responseCodec = dialect.getResponseCodec(); - return result.createResponse(); - } - if (commandCodec == null || responseCodec == null) { - throw new WebDriverException( - "No command or response codec has been defined. Unable to proceed"); - } + public AppiumCommandExecutor(Map additionalCommands, + URL addressOfRemoteServer) { + this(additionalCommands, addressOfRemoteServer, new ApacheHttpClient.Factory()); + } - HttpRequest httpRequest = commandCodec.encode(command); - try { - HttpResponse httpResponse = client.execute(httpRequest, true); - - Response response = responseCodec.decode(httpResponse); - if (response.getSessionId() == null) { - if (httpResponse.getTargetHost() != null) { - response.setSessionId(HttpSessionId.getSessionId(httpResponse.getTargetHost())); - } else { - response.setSessionId(command.getSessionId().toString()); - } - } - if (QUIT.equals(command.getName())) { - client.close(); - } - return response; - } catch (UnsupportedCommandException e) { - if (e.getMessage() == null || "".equals(e.getMessage())) { - throw new UnsupportedOperationException( - "No information from server. Command name was: " + command.getName(), - e.getCause()); - } - throw e; - } + public AppiumCommandExecutor(Map additionalCommands, + DriverService service) { + this(additionalCommands, service, new ApacheHttpClient.Factory()); } - @Override public Response execute(Command command) throws IOException, WebDriverException { - if (DriverCommand.NEW_SESSION.equals(command.getName()) && service != null) { - service.start(); + @Override public Response execute(Command command) throws WebDriverException { + if (DriverCommand.NEW_SESSION.equals(command.getName())) { + serviceOptional.ifPresent(driverService -> { + try { + driverService.start(); + } catch (IOException e) { + throw new WebDriverException(e.getMessage(), e); + } + }); } try { - return doExecute(command); + return super.execute(command); } catch (Throwable t) { - Throwable rootCause = getRootCause(t); + Throwable rootCause = Throwables.getRootCause(t); if (rootCause instanceof ConnectException - && rootCause.getMessage().contains("Connection refused") - && service != null) { - if (service.isRunning()) { - throw new WebDriverException("The session is closed!", t); - } - - if (!service.isRunning()) { - throw new WebDriverException("The appium server has accidentally died!", t); - } + && rootCause.getMessage().contains("Connection refused")) { + throw serviceOptional.map(service -> { + if (service.isRunning()) { + return new WebDriverException("The session is closed!", rootCause); + } + + return new WebDriverException("The appium server has accidentally died!", rootCause); + }).orElseGet((Supplier) () -> + new WebDriverException(rootCause.getMessage(), rootCause)); } throwIfUnchecked(t); throw new WebDriverException(t); } finally { - if (DriverCommand.QUIT.equals(command.getName()) && service != null) { - service.stop(); + if (DriverCommand.QUIT.equals(command.getName())) { + serviceOptional.ifPresent(DriverService::stop); } } } - -} +} \ No newline at end of file diff --git a/src/main/java/io/appium/java_client/remote/AppiumProtocolHandShake.java b/src/main/java/io/appium/java_client/remote/AppiumProtocolHandShake.java deleted file mode 100644 index 12dab4d39..000000000 --- a/src/main/java/io/appium/java_client/remote/AppiumProtocolHandShake.java +++ /dev/null @@ -1,217 +0,0 @@ -/* - * 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 static com.google.common.base.Charsets.UTF_8; -import static com.google.common.net.HttpHeaders.CONTENT_LENGTH; -import static com.google.common.net.HttpHeaders.CONTENT_TYPE; -import static com.google.common.net.MediaType.JSON_UTF_8; -import static java.util.Optional.ofNullable; -import static org.openqa.selenium.remote.ErrorCodes.SESSION_NOT_CREATED; -import static org.openqa.selenium.remote.ErrorCodes.SUCCESS; - -import com.google.common.base.Preconditions; - -import com.google.gson.JsonArray; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; -import com.google.gson.JsonParser; - -import org.openqa.selenium.Capabilities; -import org.openqa.selenium.SessionNotCreatedException; -import org.openqa.selenium.WebDriverException; -import org.openqa.selenium.remote.BeanToJsonConverter; -import org.openqa.selenium.remote.Command; -import org.openqa.selenium.remote.DesiredCapabilities; -import org.openqa.selenium.remote.Dialect; -import org.openqa.selenium.remote.ErrorHandler; -import org.openqa.selenium.remote.JsonException; -import org.openqa.selenium.remote.JsonToBeanConverter; -import org.openqa.selenium.remote.Response; -import org.openqa.selenium.remote.SessionId; -import org.openqa.selenium.remote.http.HttpClient; -import org.openqa.selenium.remote.http.HttpMethod; -import org.openqa.selenium.remote.http.HttpRequest; -import org.openqa.selenium.remote.http.HttpResponse; - -import java.io.IOException; -import java.net.HttpURLConnection; -import java.util.HashMap; -import java.util.Map; -import java.util.Optional; - -class AppiumProtocolHandShake { - - public Result createSession(HttpClient client, Command command) - throws IOException, WebDriverException { - - Capabilities desired = ofNullable((Capabilities) command.getParameters().get("desiredCapabilities")) - .orElseGet(DesiredCapabilities::new); - - Capabilities required = ofNullable((Capabilities) command.getParameters().get("requiredCapabilities")) - .orElseGet(DesiredCapabilities::new); - - JsonParser parser = new JsonParser(); - JsonElement des = parser.parse(new BeanToJsonConverter().convert(desired)); - JsonElement req = parser.parse(new BeanToJsonConverter().convert(required)); - - JsonObject jsonObject = new JsonObject(); - - amendW3CParameters(jsonObject, des, req); - amendOssParamters(jsonObject, des, req); - Optional result = createSession(client, jsonObject); - - return ofNullable(result.orElseGet(() -> { - JsonObject jsonObject1 = new JsonObject(); - amendOssParamters(jsonObject1, des, req); - - try { - return createSession(client, jsonObject1).orElseGet(() -> { - JsonObject jsonObject2 = new JsonObject(); - amendW3CParameters(jsonObject2, des, req); - - try { - return createSession(client, jsonObject2).orElse(null); - } catch (IOException e) { - throw new WebDriverException(e); - } - }); - } catch (IOException e) { - throw new WebDriverException(e); - } - })).orElseThrow(() -> new SessionNotCreatedException( - String.format( - "Unable to create new remote session. " - + "desired capabilities = %s, required capabilities = %s", - desired, - required))); - } - - private Optional createSession(HttpClient client, JsonObject params) - throws IOException { - // Create the http request and send it - HttpRequest request = new HttpRequest(HttpMethod.POST, "/session"); - String content = params.toString(); - byte[] data = content.getBytes(UTF_8); - - request.setHeader(CONTENT_LENGTH, String.valueOf(data.length)); - request.setHeader(CONTENT_TYPE, JSON_UTF_8.toString()); - request.setContent(data); - HttpResponse response = client.execute(request, true); - - Map jsonBlob = new HashMap<>(); - String resultString = response.getContentString(); - try { - jsonBlob = new JsonToBeanConverter().convert(Map.class, resultString); - } catch (ClassCastException e) { - return Optional.empty(); - } catch (JsonException e) { - // Fine. Handle that below - } - - // If the result looks positive, return the result. - Object sessionId = jsonBlob.get("sessionId"); - Object value = jsonBlob.get("value"); - Object w3cError = jsonBlob.get("error"); - Object ossStatus = jsonBlob.get("status"); - Map capabilities = null; - if (value != null && value instanceof Map) { - capabilities = (Map) value; - } else if (value != null && value instanceof Capabilities) { - capabilities = ((Capabilities) capabilities).asMap(); - } - - if (response.getStatus() == HttpURLConnection.HTTP_OK - && sessionId != null && capabilities != null) { - Dialect dialect = ossStatus == null ? Dialect.W3C : Dialect.OSS; - return Optional.of( - new Result(dialect, String.valueOf(sessionId), capabilities)); - } - - // If the result was an error that we believe has to do with the remote end failing to start the - // session, create an exception and throw it. - Response tempResponse = null; - if ("session not created".equals(w3cError)) { - tempResponse = new Response(null); - tempResponse.setStatus(SESSION_NOT_CREATED); - tempResponse.setValue(jsonBlob); - } else if ( - ossStatus instanceof Number - && ((Number) ossStatus).intValue() == SESSION_NOT_CREATED) { - tempResponse = new Response(null); - tempResponse.setStatus(SESSION_NOT_CREATED); - tempResponse.setValue(jsonBlob); - } - - if (tempResponse != null) { - new ErrorHandler().throwIfResponseFailed(tempResponse, 0); - } - - // Otherwise, just return empty. - return Optional.empty(); - } - - private void amendW3CParameters(JsonObject jsonObject, JsonElement desired, - JsonElement required) { - JsonArray result = new JsonArray(); - JsonObject desiredJson = new JsonObject(); - JsonObject requiredJson = new JsonObject(); - - desiredJson.add("desiredCapabilities", desired); - requiredJson.add("requiredCapabilities", required); - - result.add(desiredJson); - result.add(requiredJson); - - jsonObject.add("capabilities", result); - } - - private void amendOssParamters( - JsonObject jsonObject, JsonElement desired, - JsonElement required) { - jsonObject.add("desiredCapabilities", desired); - jsonObject.add("requiredCapabilities", required); - } - - public class Result { - private final Dialect dialect; - private final Map capabilities; - private final SessionId sessionId; - - private Result(Dialect dialect, String sessionId, Map capabilities) { - this.dialect = dialect; - this.sessionId = new SessionId(Preconditions.checkNotNull(sessionId)); - this.capabilities = capabilities; - } - - public Dialect getDialect() { - return dialect; - } - - public Response createResponse() { - Response response = new Response(sessionId); - response.setValue(capabilities); - response.setStatus(SUCCESS); - return response; - } - - @Override - public String toString() { - return String.format("%s: %s", dialect, capabilities); - } - } -} diff --git a/src/main/java/io/appium/java_client/windows/WindowsDriver.java b/src/main/java/io/appium/java_client/windows/WindowsDriver.java index 97cb366cc..5c03692ec 100644 --- a/src/main/java/io/appium/java_client/windows/WindowsDriver.java +++ b/src/main/java/io/appium/java_client/windows/WindowsDriver.java @@ -22,11 +22,11 @@ import io.appium.java_client.FindsByWindowsAutomation; import io.appium.java_client.HidesKeyboardWithKeyName; import io.appium.java_client.PressesKeyCode; -import io.appium.java_client.remote.AppiumCommandExecutor; import io.appium.java_client.service.local.AppiumDriverLocalService; import io.appium.java_client.service.local.AppiumServiceBuilder; import org.openqa.selenium.Capabilities; import org.openqa.selenium.WebElement; +import org.openqa.selenium.remote.HttpCommandExecutor; import org.openqa.selenium.remote.http.HttpClient; import java.net.URL; @@ -35,7 +35,7 @@ public class WindowsDriver extends AppiumDriver implements PressesKeyCode, HidesKeyboardWithKeyName, FindsByWindowsAutomation { - public WindowsDriver(AppiumCommandExecutor executor, Capabilities capabilities) { + public WindowsDriver(HttpCommandExecutor executor, Capabilities capabilities) { super(executor, substituteMobilePlatform(capabilities, WINDOWS)); }