From d37738a88776f9a6e4379e842b64b93f9c309c32 Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Fri, 28 Jun 2019 08:28:50 +0200 Subject: [PATCH 1/5] Add executeDriverScript command --- .../io/appium/java_client/AppiumDriver.java | 3 +- .../java_client/ExecutesDriverScript.java | 71 +++++++++++++++++++ .../io/appium/java_client/MobileCommand.java | 3 + .../driverscripts/ScriptOptions.java | 50 +++++++++++++ .../java_client/driverscripts/ScriptType.java | 5 ++ .../driverscripts/ScriptValue.java | 35 +++++++++ 6 files changed, 166 insertions(+), 1 deletion(-) create mode 100644 src/main/java/io/appium/java_client/ExecutesDriverScript.java create mode 100644 src/main/java/io/appium/java_client/driverscripts/ScriptOptions.java create mode 100644 src/main/java/io/appium/java_client/driverscripts/ScriptType.java create mode 100644 src/main/java/io/appium/java_client/driverscripts/ScriptValue.java diff --git a/src/main/java/io/appium/java_client/AppiumDriver.java b/src/main/java/io/appium/java_client/AppiumDriver.java index 4fc4c49ee..c95224da0 100644 --- a/src/main/java/io/appium/java_client/AppiumDriver.java +++ b/src/main/java/io/appium/java_client/AppiumDriver.java @@ -65,7 +65,8 @@ */ @SuppressWarnings("unchecked") public class AppiumDriver - extends DefaultGenericMobileDriver implements ComparesImages, FindsByImage, FindsByCustom { + extends DefaultGenericMobileDriver implements ComparesImages, FindsByImage, FindsByCustom, + ExecutesDriverScript { private static final ErrorHandler errorHandler = new ErrorHandler(new ErrorCodesMobile(), true); // frequently used command parameters diff --git a/src/main/java/io/appium/java_client/ExecutesDriverScript.java b/src/main/java/io/appium/java_client/ExecutesDriverScript.java new file mode 100644 index 000000000..07f00796a --- /dev/null +++ b/src/main/java/io/appium/java_client/ExecutesDriverScript.java @@ -0,0 +1,71 @@ +/* + * 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; + +import io.appium.java_client.driverscripts.ScriptOptions; +import io.appium.java_client.driverscripts.ScriptValue; +import org.openqa.selenium.remote.Response; + +import javax.annotation.Nullable; +import java.util.HashMap; +import java.util.Map; + +import static com.google.common.base.Preconditions.checkNotNull; +import static io.appium.java_client.MobileCommand.EXECUTE_DRIVER_SCRIPT; + +public interface ExecutesDriverScript extends ExecutesMethod { + + /** + * Run a set of scripts in scope of the current session. + * This allows multiple web driver commands to be executed within one request + * and may significantly speed up the automation script performance in + * distributed client-server environments with high latency. + * Read http://appium.io/docs/en/commands/session/execute-driver for more details. + * + * @since Appium 1.14 + * @param script the web driver script to execute (it should + * be a valid webdriverio code snippet by default + * unless another option is provided) + * @param options additional scripting options + * @throws org.openqa.selenium.WebDriverException if there was a failure while executing the script + * @return The script result + */ + default ScriptValue executeDriverScript(String script, @Nullable ScriptOptions options) { + Map data = new HashMap<>(); + data.put("script", checkNotNull(script)); + if (options != null) { + data.putAll(options.build()); + } + Response response = execute(EXECUTE_DRIVER_SCRIPT, data); + //noinspection unchecked + Map value = (Map) response.getValue(); + //noinspection unchecked + return new ScriptValue(value.get("result"), (Map) value.get("logs")); + } + + /** + * Run a set of scripts in scope of the current session with default options. + * + * @since Appium 1.14 + * @param script the web driver script to execute (it should + * be a valid webdriverio code snippet) + * @return The script result + */ + default ScriptValue executeDriverScript(String script) { + return executeDriverScript(script, null); + } +} diff --git a/src/main/java/io/appium/java_client/MobileCommand.java b/src/main/java/io/appium/java_client/MobileCommand.java index 4838a886a..4d2e3bffe 100644 --- a/src/main/java/io/appium/java_client/MobileCommand.java +++ b/src/main/java/io/appium/java_client/MobileCommand.java @@ -110,6 +110,7 @@ public class MobileCommand { protected static final String TOGGLE_AIRPLANE_MODE; protected static final String TOGGLE_DATA; protected static final String COMPARE_IMAGES; + protected static final String EXECUTE_DRIVER_SCRIPT; public static final Map commandRepository; @@ -184,6 +185,7 @@ public class MobileCommand { TOGGLE_AIRPLANE_MODE = "toggleFlightMode"; TOGGLE_DATA = "toggleData"; COMPARE_IMAGES = "compareImages"; + EXECUTE_DRIVER_SCRIPT = "executeDriverScript"; commandRepository = new HashMap<>(); commandRepository.put(RESET, postC("/session/:sessionId/appium/app/reset")); @@ -268,6 +270,7 @@ public class MobileCommand { commandRepository.put(TOGGLE_AIRPLANE_MODE, postC("/session/:sessionId/appium/device/toggle_airplane_mode")); commandRepository.put(TOGGLE_DATA, postC("/session/:sessionId/appium/device/toggle_data")); commandRepository.put(COMPARE_IMAGES, postC("/session/:sessionId/appium/compare_images")); + commandRepository.put(EXECUTE_DRIVER_SCRIPT, postC("/session/:sessionId/appium/execute_driver")); } /** diff --git a/src/main/java/io/appium/java_client/driverscripts/ScriptOptions.java b/src/main/java/io/appium/java_client/driverscripts/ScriptOptions.java new file mode 100644 index 000000000..6e31c23ed --- /dev/null +++ b/src/main/java/io/appium/java_client/driverscripts/ScriptOptions.java @@ -0,0 +1,50 @@ +package io.appium.java_client.driverscripts; + +import com.google.common.collect.ImmutableMap; + +import java.util.Map; + +import static com.google.common.base.Preconditions.checkNotNull; +import static java.util.Optional.ofNullable; + + +public class ScriptOptions { + private ScriptType scriptType = ScriptType.WEBDRIVERIO; + private Long timeoutMs; + + /** + * Sets the script type. + * + * @param type the actual script type + * @return self instance for chaining + */ + public ScriptOptions withScriptType(ScriptType type) { + this.scriptType = checkNotNull(type); + return this; + } + + /** + * Sets the script execution timeout. + * If this is not set the the maximum duration of the script + * is not limited (e. g. may block forever) + * + * @param timeoutMs the timeout in milliseconds + * @return self instance for chaining + */ + public ScriptOptions withTimeout(long timeoutMs) { + this.timeoutMs = timeoutMs; + return this; + } + + /** + * Builds a values map for further usage in HTTP requests to Appium. + * + * @return The map containing the provided options + */ + public Map build() { + final ImmutableMap.Builder builder = ImmutableMap.builder(); + ofNullable(scriptType).map(x -> builder.put("type", x.name().toLowerCase())); + ofNullable(timeoutMs).map(x -> builder.put("timeout", x)); + return builder.build(); + } +} diff --git a/src/main/java/io/appium/java_client/driverscripts/ScriptType.java b/src/main/java/io/appium/java_client/driverscripts/ScriptType.java new file mode 100644 index 000000000..7286ae4f9 --- /dev/null +++ b/src/main/java/io/appium/java_client/driverscripts/ScriptType.java @@ -0,0 +1,5 @@ +package io.appium.java_client.driverscripts; + +public enum ScriptType { + WEBDRIVERIO +} diff --git a/src/main/java/io/appium/java_client/driverscripts/ScriptValue.java b/src/main/java/io/appium/java_client/driverscripts/ScriptValue.java new file mode 100644 index 000000000..3cd6dd303 --- /dev/null +++ b/src/main/java/io/appium/java_client/driverscripts/ScriptValue.java @@ -0,0 +1,35 @@ +package io.appium.java_client.driverscripts; + +import javax.annotation.Nullable; +import java.util.Map; + +public class ScriptValue { + private final Object result; + private final Map logs; + + public ScriptValue(Object result, Map logs) { + this.result = result; + this.logs = logs; + } + + /** + * The result of ExecuteDriverScript call. + * + * @return The actual returned value depends on the script content + */ + @Nullable + public Object getResult() { + return result; + } + + /** + * Retrieves logs mapping from ExecuteDriverScript call. + * + * @return Mapping keys are log levels, for example `warn` or + * `error` and the values are lists of strings that were printed + * by the script into the corresponding logging level + */ + public Map getLogs() { + return logs; + } +} From 44d25ceed07f2c4644705e599fc415a1f78f4988 Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Fri, 28 Jun 2019 08:29:26 +0200 Subject: [PATCH 2/5] Add headers --- .../java_client/driverscripts/ScriptOptions.java | 16 ++++++++++++++++ .../java_client/driverscripts/ScriptType.java | 16 ++++++++++++++++ .../java_client/driverscripts/ScriptValue.java | 16 ++++++++++++++++ 3 files changed, 48 insertions(+) diff --git a/src/main/java/io/appium/java_client/driverscripts/ScriptOptions.java b/src/main/java/io/appium/java_client/driverscripts/ScriptOptions.java index 6e31c23ed..5fdd14724 100644 --- a/src/main/java/io/appium/java_client/driverscripts/ScriptOptions.java +++ b/src/main/java/io/appium/java_client/driverscripts/ScriptOptions.java @@ -1,3 +1,19 @@ +/* + * 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.driverscripts; import com.google.common.collect.ImmutableMap; diff --git a/src/main/java/io/appium/java_client/driverscripts/ScriptType.java b/src/main/java/io/appium/java_client/driverscripts/ScriptType.java index 7286ae4f9..42aa78833 100644 --- a/src/main/java/io/appium/java_client/driverscripts/ScriptType.java +++ b/src/main/java/io/appium/java_client/driverscripts/ScriptType.java @@ -1,3 +1,19 @@ +/* + * 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.driverscripts; public enum ScriptType { diff --git a/src/main/java/io/appium/java_client/driverscripts/ScriptValue.java b/src/main/java/io/appium/java_client/driverscripts/ScriptValue.java index 3cd6dd303..a01bce38e 100644 --- a/src/main/java/io/appium/java_client/driverscripts/ScriptValue.java +++ b/src/main/java/io/appium/java_client/driverscripts/ScriptValue.java @@ -1,3 +1,19 @@ +/* + * 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.driverscripts; import javax.annotation.Nullable; From 6a88c765acbd406d473eb972e999308e898259ce Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Fri, 28 Jun 2019 08:32:06 +0200 Subject: [PATCH 3/5] Tune docstring --- .../io/appium/java_client/driverscripts/ScriptOptions.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/io/appium/java_client/driverscripts/ScriptOptions.java b/src/main/java/io/appium/java_client/driverscripts/ScriptOptions.java index 5fdd14724..adb46fbb3 100644 --- a/src/main/java/io/appium/java_client/driverscripts/ScriptOptions.java +++ b/src/main/java/io/appium/java_client/driverscripts/ScriptOptions.java @@ -41,8 +41,8 @@ public ScriptOptions withScriptType(ScriptType type) { /** * Sets the script execution timeout. - * If this is not set the the maximum duration of the script - * is not limited (e. g. may block forever) + * If this is not set then the maximum duration of the script + * is not limited (e. g. may block forever). * * @param timeoutMs the timeout in milliseconds * @return self instance for chaining From 6a47335ac13db8878c7a285aab0c4d467a7cf4bc Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Fri, 28 Jun 2019 08:33:02 +0200 Subject: [PATCH 4/5] Do not set the default value --- .../java/io/appium/java_client/driverscripts/ScriptOptions.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/io/appium/java_client/driverscripts/ScriptOptions.java b/src/main/java/io/appium/java_client/driverscripts/ScriptOptions.java index adb46fbb3..15d7ddf36 100644 --- a/src/main/java/io/appium/java_client/driverscripts/ScriptOptions.java +++ b/src/main/java/io/appium/java_client/driverscripts/ScriptOptions.java @@ -25,7 +25,7 @@ public class ScriptOptions { - private ScriptType scriptType = ScriptType.WEBDRIVERIO; + private ScriptType scriptType; private Long timeoutMs; /** From fea0bae15d94c68895db239182409b8d1d96ad81 Mon Sep 17 00:00:00 2001 From: Mykola Mokhnach Date: Fri, 28 Jun 2019 19:00:10 +0200 Subject: [PATCH 5/5] Add tests --- .../java_client/ExecutesDriverScript.java | 2 +- .../driverscripts/ScriptValue.java | 6 +-- .../android/ExecuteDriverScriptTest.java | 51 +++++++++++++++++++ 3 files changed, 54 insertions(+), 5 deletions(-) create mode 100644 src/test/java/io/appium/java_client/android/ExecuteDriverScriptTest.java diff --git a/src/main/java/io/appium/java_client/ExecutesDriverScript.java b/src/main/java/io/appium/java_client/ExecutesDriverScript.java index 07f00796a..997b061a2 100644 --- a/src/main/java/io/appium/java_client/ExecutesDriverScript.java +++ b/src/main/java/io/appium/java_client/ExecutesDriverScript.java @@ -41,8 +41,8 @@ public interface ExecutesDriverScript extends ExecutesMethod { * be a valid webdriverio code snippet by default * unless another option is provided) * @param options additional scripting options - * @throws org.openqa.selenium.WebDriverException if there was a failure while executing the script * @return The script result + * @throws org.openqa.selenium.WebDriverException if there was a failure while executing the script */ default ScriptValue executeDriverScript(String script, @Nullable ScriptOptions options) { Map data = new HashMap<>(); diff --git a/src/main/java/io/appium/java_client/driverscripts/ScriptValue.java b/src/main/java/io/appium/java_client/driverscripts/ScriptValue.java index a01bce38e..3949feaa1 100644 --- a/src/main/java/io/appium/java_client/driverscripts/ScriptValue.java +++ b/src/main/java/io/appium/java_client/driverscripts/ScriptValue.java @@ -16,7 +16,6 @@ package io.appium.java_client.driverscripts; -import javax.annotation.Nullable; import java.util.Map; public class ScriptValue { @@ -33,7 +32,6 @@ public ScriptValue(Object result, Map logs) { * * @return The actual returned value depends on the script content */ - @Nullable public Object getResult() { return result; } @@ -42,8 +40,8 @@ public Object getResult() { * Retrieves logs mapping from ExecuteDriverScript call. * * @return Mapping keys are log levels, for example `warn` or - * `error` and the values are lists of strings that were printed - * by the script into the corresponding logging level + * `error` and the values are lists of strings that were printed + * by the script into the corresponding logging level */ public Map getLogs() { return logs; diff --git a/src/test/java/io/appium/java_client/android/ExecuteDriverScriptTest.java b/src/test/java/io/appium/java_client/android/ExecuteDriverScriptTest.java new file mode 100644 index 000000000..5b8da2938 --- /dev/null +++ b/src/test/java/io/appium/java_client/android/ExecuteDriverScriptTest.java @@ -0,0 +1,51 @@ +/* + * 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.android; + +import io.appium.java_client.driverscripts.ScriptOptions; +import io.appium.java_client.driverscripts.ScriptType; +import io.appium.java_client.driverscripts.ScriptValue; +import org.junit.Test; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.core.Is.is; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertThat; + +public class ExecuteDriverScriptTest extends BaseAndroidTest { + + @Test + public void verifyBasicScriptExecution() { + String script = String.join("\n", Arrays.asList( + "const status = await driver.status();", + "console.warn('warning message');", + "return status;") + ); + ScriptValue value = driver.executeDriverScript(script, new ScriptOptions() + .withTimeout(5000) + .withScriptType(ScriptType.WEBDRIVERIO)); + //noinspection unchecked + assertNotNull(((Map) value.getResult()).get("build")); + //noinspection unchecked + assertThat(((List)value.getLogs().get("warn")).get(0), + is(equalTo("warning message"))); + } +}