From bd9e0761107f2cad81267d3e97b22e256e336251 Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Thu, 21 Oct 2021 10:00:42 +0200 Subject: [PATCH 1/8] refactor: Make sure we only write W3C payload into create session ocmmand --- .../remote/AppiumCommandExecutor.java | 2 +- .../remote/AppiumProtocolHandshake.java | 100 ++++++++++++++++++ 2 files changed, 101 insertions(+), 1 deletion(-) create mode 100644 src/main/java/io/appium/java_client/remote/AppiumProtocolHandshake.java 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 e98d1a2d9..0371748fe 100644 --- a/src/main/java/io/appium/java_client/remote/AppiumCommandExecutor.java +++ b/src/main/java/io/appium/java_client/remote/AppiumCommandExecutor.java @@ -145,7 +145,7 @@ private Response createSession(Command command) throws IOException { throw new SessionNotCreatedException("Session already exists"); } - ProtocolHandshake.Result result = new ProtocolHandshake().createSession( + ProtocolHandshake.Result result = new AppiumProtocolHandshake().createSession( getClient().with((httpHandler) -> (req) -> { req.setHeader(IDEMPOTENCY_KEY_HEADER, UUID.randomUUID().toString().toLowerCase()); return httpHandler.execute(req); diff --git a/src/main/java/io/appium/java_client/remote/AppiumProtocolHandshake.java b/src/main/java/io/appium/java_client/remote/AppiumProtocolHandshake.java new file mode 100644 index 000000000..6fdd3a56b --- /dev/null +++ b/src/main/java/io/appium/java_client/remote/AppiumProtocolHandshake.java @@ -0,0 +1,100 @@ +/* + * 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 com.google.common.io.CountingOutputStream; +import com.google.common.io.FileBackedOutputStream; +import org.openqa.selenium.SessionNotCreatedException; +import org.openqa.selenium.WebDriverException; +import org.openqa.selenium.internal.Either; +import org.openqa.selenium.json.Json; +import org.openqa.selenium.json.JsonOutput; +import org.openqa.selenium.remote.NewSessionPayload; +import org.openqa.selenium.remote.ProtocolHandshake; +import org.openqa.selenium.remote.http.HttpHandler; + +import java.io.BufferedInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStreamWriter; +import java.io.Writer; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Map; +import java.util.stream.Stream; + +import static java.nio.charset.StandardCharsets.UTF_8; + +@SuppressWarnings("UnstableApiUsage") +public class AppiumProtocolHandshake extends ProtocolHandshake { + private static void writeJsonPayload(NewSessionPayload srcPayload, Appendable destination) { + try (JsonOutput json = new Json().newOutput(destination)) { + json.beginObject(); + + json.name("capabilities"); + json.beginObject(); + + json.name("firstMatch"); + json.beginArray(); + try { + Method getW3CMethod = srcPayload.getClass().getDeclaredMethod("getW3C"); + getW3CMethod.setAccessible(true); + //noinspection unchecked + ((Stream>) getW3CMethod.invoke(srcPayload)).forEach(json::write); + } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) { + throw new WebDriverException(e); + } + json.endArray(); + + json.endObject(); // Close "capabilities" object + + try { + Method writeMetaDataMethod = srcPayload.getClass().getDeclaredMethod("writeMetaData", JsonOutput.class); + writeMetaDataMethod.setAccessible(true); + writeMetaDataMethod.invoke(srcPayload, json); + } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) { + throw new WebDriverException(e); + } + + json.endObject(); + } + } + + public Either createSession(HttpHandler client, NewSessionPayload payload) throws IOException { + int threshold = (int) Math.min(Runtime.getRuntime().freeMemory() / 10, Integer.MAX_VALUE); + FileBackedOutputStream os = new FileBackedOutputStream(threshold); + + try (CountingOutputStream counter = new CountingOutputStream(os); + Writer writer = new OutputStreamWriter(counter, UTF_8)) { + writeJsonPayload(payload, writer); + + try (InputStream rawIn = os.asByteSource().openBufferedStream(); + BufferedInputStream contentStream = new BufferedInputStream(rawIn)) { + Method createSessionMethod = super.getClass().getDeclaredMethod("createSession", + HttpHandler.class, InputStream.class, long.class); + createSessionMethod.setAccessible(true); + //noinspection unchecked + return (Either) createSessionMethod.invoke( + this, client, contentStream, counter.getCount() + ); + } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) { + throw new WebDriverException(e); + } + } finally { + os.reset(); + } + } +} From 0445b4300be604d2f0560a500f3f7c22f5047754 Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Thu, 21 Oct 2021 10:04:32 +0200 Subject: [PATCH 2/8] Add override --- .../io/appium/java_client/remote/AppiumProtocolHandshake.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/io/appium/java_client/remote/AppiumProtocolHandshake.java b/src/main/java/io/appium/java_client/remote/AppiumProtocolHandshake.java index 6fdd3a56b..60ef6dd19 100644 --- a/src/main/java/io/appium/java_client/remote/AppiumProtocolHandshake.java +++ b/src/main/java/io/appium/java_client/remote/AppiumProtocolHandshake.java @@ -73,6 +73,7 @@ private static void writeJsonPayload(NewSessionPayload srcPayload, Appendable de } } + @Override public Either createSession(HttpHandler client, NewSessionPayload payload) throws IOException { int threshold = (int) Math.min(Runtime.getRuntime().freeMemory() / 10, Integer.MAX_VALUE); FileBackedOutputStream os = new FileBackedOutputStream(threshold); From 5cda73fd09a5c86f033f857a5c55dbc488dfe2d4 Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Thu, 21 Oct 2021 10:36:35 +0200 Subject: [PATCH 3/8] Fix getting the method --- .../io/appium/java_client/remote/AppiumProtocolHandshake.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/io/appium/java_client/remote/AppiumProtocolHandshake.java b/src/main/java/io/appium/java_client/remote/AppiumProtocolHandshake.java index 60ef6dd19..940c69066 100644 --- a/src/main/java/io/appium/java_client/remote/AppiumProtocolHandshake.java +++ b/src/main/java/io/appium/java_client/remote/AppiumProtocolHandshake.java @@ -84,7 +84,7 @@ public Either createSession(HttpHandler clie try (InputStream rawIn = os.asByteSource().openBufferedStream(); BufferedInputStream contentStream = new BufferedInputStream(rawIn)) { - Method createSessionMethod = super.getClass().getDeclaredMethod("createSession", + Method createSessionMethod = getClass().getSuperclass().getDeclaredMethod("createSession", HttpHandler.class, InputStream.class, long.class); createSessionMethod.setAccessible(true); //noinspection unchecked From f612e4bde3994772961dc9ad385c011534845dc0 Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Thu, 21 Oct 2021 19:38:49 +0200 Subject: [PATCH 4/8] Patch capability names if needed --- .../io/appium/java_client/AppiumDriver.java | 39 +++++++++++--- .../AppiumNewSessionCommandPayload.java | 53 +++++++++++++++++++ 2 files changed, 84 insertions(+), 8 deletions(-) create mode 100644 src/main/java/io/appium/java_client/remote/AppiumNewSessionCommandPayload.java diff --git a/src/main/java/io/appium/java_client/AppiumDriver.java b/src/main/java/io/appium/java_client/AppiumDriver.java index 1e880c569..5b9cff823 100644 --- a/src/main/java/io/appium/java_client/AppiumDriver.java +++ b/src/main/java/io/appium/java_client/AppiumDriver.java @@ -26,6 +26,7 @@ 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.AppiumNewSessionCommandPayload; import io.appium.java_client.remote.MobileCapabilityType; import io.appium.java_client.service.local.AppiumDriverLocalService; import io.appium.java_client.service.local.AppiumServiceBuilder; @@ -34,6 +35,7 @@ import org.openqa.selenium.DeviceRotation; import org.openqa.selenium.MutableCapabilities; import org.openqa.selenium.ScreenOrientation; +import org.openqa.selenium.SessionNotCreatedException; import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebDriverException; import org.openqa.selenium.WebElement; @@ -44,11 +46,13 @@ import org.openqa.selenium.remote.ErrorHandler; import org.openqa.selenium.remote.ExecuteMethod; import org.openqa.selenium.remote.HttpCommandExecutor; +import org.openqa.selenium.remote.RemoteWebDriver; import org.openqa.selenium.remote.Response; import org.openqa.selenium.remote.html5.RemoteLocationContext; import org.openqa.selenium.remote.http.HttpClient; import org.openqa.selenium.remote.http.HttpMethod; +import java.lang.reflect.Field; import java.net.URL; import java.util.Arrays; import java.util.LinkedHashSet; @@ -299,14 +303,33 @@ public boolean isBrowser() { @Override protected void startSession(Capabilities capabilities) { - super.startSession(capabilities); - // The RemoteWebDriver implementation overrides platformName - // so we need to restore it back to the original value - Object originalPlatformName = capabilities.getCapability(PLATFORM_NAME); - Capabilities originalCaps = super.getCapabilities(); - if (originalPlatformName != null && originalCaps instanceof MutableCapabilities) { - ((MutableCapabilities) super.getCapabilities()).setCapability(PLATFORM_NAME, - originalPlatformName); + Response response = execute(new AppiumNewSessionCommandPayload(capabilities)); + if (response == null) { + throw new SessionNotCreatedException( + "The underlying command executor returned a null response."); } + + Object responseValue = response.getValue(); + if (responseValue == null) { + throw new SessionNotCreatedException( + "The underlying command executor returned a response without payload: " + + response); + } + if (!(responseValue instanceof Map)) { + throw new SessionNotCreatedException( + "The underlying command executor returned a response with a non well formed payload: " + + response); + } + + @SuppressWarnings("unchecked") Map rawCapabilities = (Map) responseValue; + MutableCapabilities returnedCapabilities = new MutableCapabilities(rawCapabilities); + try { + Field capsField = RemoteWebDriver.class.getDeclaredField("capabilities"); + capsField.setAccessible(true); + capsField.set(this, returnedCapabilities); + } catch (NoSuchFieldException | IllegalAccessException e) { + throw new WebDriverException(e); + } + setSessionId(response.getSessionId()); } } diff --git a/src/main/java/io/appium/java_client/remote/AppiumNewSessionCommandPayload.java b/src/main/java/io/appium/java_client/remote/AppiumNewSessionCommandPayload.java new file mode 100644 index 000000000..f7918e6c4 --- /dev/null +++ b/src/main/java/io/appium/java_client/remote/AppiumNewSessionCommandPayload.java @@ -0,0 +1,53 @@ +/* + * 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 com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import org.openqa.selenium.Capabilities; +import org.openqa.selenium.internal.Require; +import org.openqa.selenium.remote.AcceptedW3CCapabilityKeys; +import org.openqa.selenium.remote.CommandPayload; + +import java.util.AbstractMap; +import java.util.Map; +import java.util.Set; + +import static io.appium.java_client.internal.CapabilityHelpers.APPIUM_PREFIX; +import static org.openqa.selenium.remote.DriverCommand.NEW_SESSION; + +public class AppiumNewSessionCommandPayload extends CommandPayload { + private static final AcceptedW3CCapabilityKeys ACCEPTED_W3C_PATTERNS = new AcceptedW3CCapabilityKeys(); + + private static Set> makeW3CSafe(Capabilities possiblyInvalidCapabilities) { + Require.nonNull("Capabilities", possiblyInvalidCapabilities); + + return ImmutableSet.of(possiblyInvalidCapabilities.asMap().entrySet().stream() + .map((entry) -> ACCEPTED_W3C_PATTERNS.test(entry.getKey()) + ? entry + : new AbstractMap.SimpleEntry<>( + String.format("%s%s", APPIUM_PREFIX, entry.getKey()), entry.getValue())) + .collect(ImmutableMap.toImmutableMap(Map.Entry::getKey, Map.Entry::getValue))); + } + + public AppiumNewSessionCommandPayload(Capabilities capabilities) { + super(NEW_SESSION, ImmutableMap.of( + "capabilities", makeW3CSafe(capabilities), + "desiredCapabilities", capabilities + )); + } +} From 4090d0cde1f8dfe21674144db583c376f83e8211 Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Thu, 21 Oct 2021 19:44:13 +0200 Subject: [PATCH 5/8] Fix checkstyle --- .../io/appium/java_client/remote/AppiumProtocolHandshake.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/io/appium/java_client/remote/AppiumProtocolHandshake.java b/src/main/java/io/appium/java_client/remote/AppiumProtocolHandshake.java index 940c69066..2bbccdaba 100644 --- a/src/main/java/io/appium/java_client/remote/AppiumProtocolHandshake.java +++ b/src/main/java/io/appium/java_client/remote/AppiumProtocolHandshake.java @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package io.appium.java_client.remote; import com.google.common.io.CountingOutputStream; From 1573425ccb7fb3f488b51a580e1a5805bf38e412 Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Thu, 21 Oct 2021 20:10:36 +0200 Subject: [PATCH 6/8] Fix linting --- .../io/appium/java_client/AppiumDriver.java | 8 ++--- .../AppiumNewSessionCommandPayload.java | 15 ++++++--- .../remote/AppiumProtocolHandshake.java | 31 ++++++++++++++++--- 3 files changed, 41 insertions(+), 13 deletions(-) diff --git a/src/main/java/io/appium/java_client/AppiumDriver.java b/src/main/java/io/appium/java_client/AppiumDriver.java index 5b9cff823..5c60ada7f 100644 --- a/src/main/java/io/appium/java_client/AppiumDriver.java +++ b/src/main/java/io/appium/java_client/AppiumDriver.java @@ -312,13 +312,13 @@ protected void startSession(Capabilities capabilities) { Object responseValue = response.getValue(); if (responseValue == null) { throw new SessionNotCreatedException( - "The underlying command executor returned a response without payload: " + - response); + "The underlying command executor returned a response without payload: " + + response); } if (!(responseValue instanceof Map)) { throw new SessionNotCreatedException( - "The underlying command executor returned a response with a non well formed payload: " + - response); + "The underlying command executor returned a response with a non well formed payload: " + + response); } @SuppressWarnings("unchecked") Map rawCapabilities = (Map) responseValue; diff --git a/src/main/java/io/appium/java_client/remote/AppiumNewSessionCommandPayload.java b/src/main/java/io/appium/java_client/remote/AppiumNewSessionCommandPayload.java index f7918e6c4..98135f3b1 100644 --- a/src/main/java/io/appium/java_client/remote/AppiumNewSessionCommandPayload.java +++ b/src/main/java/io/appium/java_client/remote/AppiumNewSessionCommandPayload.java @@ -25,7 +25,6 @@ import java.util.AbstractMap; import java.util.Map; -import java.util.Set; import static io.appium.java_client.internal.CapabilityHelpers.APPIUM_PREFIX; import static org.openqa.selenium.remote.DriverCommand.NEW_SESSION; @@ -33,20 +32,26 @@ public class AppiumNewSessionCommandPayload extends CommandPayload { private static final AcceptedW3CCapabilityKeys ACCEPTED_W3C_PATTERNS = new AcceptedW3CCapabilityKeys(); - private static Set> makeW3CSafe(Capabilities possiblyInvalidCapabilities) { + /** + * Appends "appium:" prefix to all non-prefixed non-standard capabilities. + * + * @param possiblyInvalidCapabilities user-provided capabilities mapping. + * @return Fixed capabilities mapping. + */ + private static Map makeW3CSafe(Capabilities possiblyInvalidCapabilities) { Require.nonNull("Capabilities", possiblyInvalidCapabilities); - return ImmutableSet.of(possiblyInvalidCapabilities.asMap().entrySet().stream() + return possiblyInvalidCapabilities.asMap().entrySet().stream() .map((entry) -> ACCEPTED_W3C_PATTERNS.test(entry.getKey()) ? entry : new AbstractMap.SimpleEntry<>( String.format("%s%s", APPIUM_PREFIX, entry.getKey()), entry.getValue())) - .collect(ImmutableMap.toImmutableMap(Map.Entry::getKey, Map.Entry::getValue))); + .collect(ImmutableMap.toImmutableMap(Map.Entry::getKey, Map.Entry::getValue)); } public AppiumNewSessionCommandPayload(Capabilities capabilities) { super(NEW_SESSION, ImmutableMap.of( - "capabilities", makeW3CSafe(capabilities), + "capabilities", ImmutableSet.of(makeW3CSafe(capabilities)), "desiredCapabilities", capabilities )); } diff --git a/src/main/java/io/appium/java_client/remote/AppiumProtocolHandshake.java b/src/main/java/io/appium/java_client/remote/AppiumProtocolHandshake.java index 2bbccdaba..02dd90729 100644 --- a/src/main/java/io/appium/java_client/remote/AppiumProtocolHandshake.java +++ b/src/main/java/io/appium/java_client/remote/AppiumProtocolHandshake.java @@ -18,11 +18,14 @@ import com.google.common.io.CountingOutputStream; import com.google.common.io.FileBackedOutputStream; +import org.openqa.selenium.Capabilities; +import org.openqa.selenium.ImmutableCapabilities; import org.openqa.selenium.SessionNotCreatedException; import org.openqa.selenium.WebDriverException; import org.openqa.selenium.internal.Either; import org.openqa.selenium.json.Json; import org.openqa.selenium.json.JsonOutput; +import org.openqa.selenium.remote.Command; import org.openqa.selenium.remote.NewSessionPayload; import org.openqa.selenium.remote.ProtocolHandshake; import org.openqa.selenium.remote.http.HttpHandler; @@ -35,6 +38,7 @@ import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Map; +import java.util.Set; import java.util.stream.Stream; import static java.nio.charset.StandardCharsets.UTF_8; @@ -51,7 +55,7 @@ private static void writeJsonPayload(NewSessionPayload srcPayload, Appendable de json.name("firstMatch"); json.beginArray(); try { - Method getW3CMethod = srcPayload.getClass().getDeclaredMethod("getW3C"); + Method getW3CMethod = NewSessionPayload.class.getDeclaredMethod("getW3C"); getW3CMethod.setAccessible(true); //noinspection unchecked ((Stream>) getW3CMethod.invoke(srcPayload)).forEach(json::write); @@ -63,7 +67,8 @@ private static void writeJsonPayload(NewSessionPayload srcPayload, Appendable de json.endObject(); // Close "capabilities" object try { - Method writeMetaDataMethod = srcPayload.getClass().getDeclaredMethod("writeMetaData", JsonOutput.class); + Method writeMetaDataMethod = NewSessionPayload.class.getDeclaredMethod( + "writeMetaData", JsonOutput.class); writeMetaDataMethod.setAccessible(true); writeMetaDataMethod.invoke(srcPayload, json); } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) { @@ -75,7 +80,25 @@ private static void writeJsonPayload(NewSessionPayload srcPayload, Appendable de } @Override - public Either createSession(HttpHandler client, NewSessionPayload payload) throws IOException { + public Result createSession(HttpHandler client, Command command) throws IOException { + //noinspection unchecked + Capabilities desired = ((Set>) command.getParameters().get("capabilities")) + .stream() + .findAny() + .map(ImmutableCapabilities::new) + .orElseGet(ImmutableCapabilities::new); + try (NewSessionPayload payload = NewSessionPayload.create(desired)) { + Either result = createSession(client, payload); + if (result.isRight()) { + return result.right(); + } + throw result.left(); + } + } + + @Override + public Either createSession( + HttpHandler client, NewSessionPayload payload) throws IOException { int threshold = (int) Math.min(Runtime.getRuntime().freeMemory() / 10, Integer.MAX_VALUE); FileBackedOutputStream os = new FileBackedOutputStream(threshold); @@ -85,7 +108,7 @@ public Either createSession(HttpHandler clie try (InputStream rawIn = os.asByteSource().openBufferedStream(); BufferedInputStream contentStream = new BufferedInputStream(rawIn)) { - Method createSessionMethod = getClass().getSuperclass().getDeclaredMethod("createSession", + Method createSessionMethod = ProtocolHandshake.class.getDeclaredMethod("createSession", HttpHandler.class, InputStream.class, long.class); createSessionMethod.setAccessible(true); //noinspection unchecked From 9aeb72d9ab64fcfcdb1c1d0b6989598a2edf8843 Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Thu, 21 Oct 2021 20:47:27 +0200 Subject: [PATCH 7/8] Write into alwaysMatch --- .../java_client/remote/AppiumNewSessionCommandPayload.java | 6 ++++++ .../appium/java_client/remote/AppiumProtocolHandshake.java | 4 +++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/main/java/io/appium/java_client/remote/AppiumNewSessionCommandPayload.java b/src/main/java/io/appium/java_client/remote/AppiumNewSessionCommandPayload.java index 98135f3b1..fd81d801c 100644 --- a/src/main/java/io/appium/java_client/remote/AppiumNewSessionCommandPayload.java +++ b/src/main/java/io/appium/java_client/remote/AppiumNewSessionCommandPayload.java @@ -49,6 +49,12 @@ private static Map makeW3CSafe(Capabilities possiblyInvalidCapab .collect(ImmutableMap.toImmutableMap(Map.Entry::getKey, Map.Entry::getValue)); } + /** + * Overrides the default new session behavior to + * only handle W3C capabilities. + * + * @param capabilities User-provided capabilities. + */ public AppiumNewSessionCommandPayload(Capabilities capabilities) { super(NEW_SESSION, ImmutableMap.of( "capabilities", ImmutableSet.of(makeW3CSafe(capabilities)), diff --git a/src/main/java/io/appium/java_client/remote/AppiumProtocolHandshake.java b/src/main/java/io/appium/java_client/remote/AppiumProtocolHandshake.java index 02dd90729..76526eb18 100644 --- a/src/main/java/io/appium/java_client/remote/AppiumProtocolHandshake.java +++ b/src/main/java/io/appium/java_client/remote/AppiumProtocolHandshake.java @@ -54,6 +54,9 @@ private static void writeJsonPayload(NewSessionPayload srcPayload, Appendable de json.name("firstMatch"); json.beginArray(); + json.endArray(); + + json.name("alwaysMatch"); try { Method getW3CMethod = NewSessionPayload.class.getDeclaredMethod("getW3C"); getW3CMethod.setAccessible(true); @@ -62,7 +65,6 @@ private static void writeJsonPayload(NewSessionPayload srcPayload, Appendable de } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) { throw new WebDriverException(e); } - json.endArray(); json.endObject(); // Close "capabilities" object From b68d7282d6acbe80c02c5d365bf14383e6760cba Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Thu, 21 Oct 2021 20:56:25 +0200 Subject: [PATCH 8/8] Handle W3C properly --- .../java_client/remote/AppiumProtocolHandshake.java | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/main/java/io/appium/java_client/remote/AppiumProtocolHandshake.java b/src/main/java/io/appium/java_client/remote/AppiumProtocolHandshake.java index 76526eb18..98b128554 100644 --- a/src/main/java/io/appium/java_client/remote/AppiumProtocolHandshake.java +++ b/src/main/java/io/appium/java_client/remote/AppiumProtocolHandshake.java @@ -54,6 +54,8 @@ private static void writeJsonPayload(NewSessionPayload srcPayload, Appendable de json.name("firstMatch"); json.beginArray(); + json.beginObject(); + json.endObject(); json.endArray(); json.name("alwaysMatch"); @@ -61,7 +63,14 @@ private static void writeJsonPayload(NewSessionPayload srcPayload, Appendable de Method getW3CMethod = NewSessionPayload.class.getDeclaredMethod("getW3C"); getW3CMethod.setAccessible(true); //noinspection unchecked - ((Stream>) getW3CMethod.invoke(srcPayload)).forEach(json::write); + ((Stream>) getW3CMethod.invoke(srcPayload)) + .findFirst() + .map(json::write) + .orElseGet(() -> { + json.beginObject(); + json.endObject(); + return null; + }); } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) { throw new WebDriverException(e); }