diff --git a/.azure-templates/bootstrap_steps.yml b/.azure-templates/bootstrap_steps.yml index 9f4e3032b..baf4b6979 100644 --- a/.azure-templates/bootstrap_steps.yml +++ b/.azure-templates/bootstrap_steps.yml @@ -7,4 +7,4 @@ steps: npm config set prefix $NVM_DIR/versions/node/`node --version` node --version - npm install -g appium@beta + npm install -g appium@next diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 67d36f46e..51703c04f 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -14,13 +14,15 @@ variables: XCODE_VERSION: 11.5 IOS_PLATFORM_VERSION: 13.5 IOS_DEVICE_NAME: iPhone X - NODE_VERSION: 12.x + NODE_VERSION: 14.x JDK_VERSION: 1.8 jobs: - job: Android_E2E_Tests steps: - template: .azure-templates/bootstrap_steps.yml + - script: $NVM_DIR/versions/node/`node --version`/bin/appium driver install uiautomator2 + displayName: Install UIA2 driver - script: | echo "y" | $ANDROID_HOME/tools/bin/sdkmanager --install 'system-images;$(ANDROID_EMU_TARGET);$(ANDROID_EMU_TAG);$(ANDROID_EMU_ABI)' echo "no" | $ANDROID_HOME/tools/bin/avdmanager create avd -n "$(ANDROID_EMU_NAME)" -k 'system-images;$(ANDROID_EMU_TARGET);$(ANDROID_EMU_TAG);$(ANDROID_EMU_ABI)' --force @@ -50,6 +52,8 @@ jobs: sudo xcode-select -s /Applications/Xcode_$(XCODE_VERSION).app/Contents/Developer xcrun simctl list displayName: Simulator configuration + - script: $NVM_DIR/versions/node/`node --version`/bin/appium driver install xcuitest + displayName: Install XCUITest driver - task: Gradle@2 inputs: gradleWrapperFile: 'gradlew' diff --git a/docs/v7-to-v8-migration-guide.md b/docs/v7-to-v8-migration-guide.md index cdac6ee53..72cdc08c6 100644 --- a/docs/v7-to-v8-migration-guide.md +++ b/docs/v7-to-v8-migration-guide.md @@ -96,3 +96,15 @@ for more details on how to properly apply W3C Actions to your automation context - AppiumDriver methods `resetApp`, `launchApp` and `closeApp` have been deprecated as they are going to be removed from future Appium versions. Check https://github.com/appium/appium/issues/15807 for more details. + +## AppiumDriverLocalService + +- The default URL the server is listening on has been changed, and it +does not contain the `/wd/hub` suffix anymore (e.g. `http://0.0.0.0:4723/wd/hub` +became `http://0.0.0.0:4723/`). This has been done in order +to align the actual behavior with Appium v2. If you still would like to use +v8 of the Java client with Appium v1.2x, where the server URL contains the `/wd/hub` suffix +by default, then consider providing `--base-path` setting explicitly while +building `AppiumServiceBuilder` instance (e.g. `.withArgument(GeneralServerFlag.BASEPATH, "/wd/hub")`). +Older versions of Appium server (v1.19 and older) won't work with `AppiumDriverLocalService`, +because they don't allow provisioning of base path in form of a command line argument. diff --git a/src/main/java/io/appium/java_client/service/local/AppiumDriverLocalService.java b/src/main/java/io/appium/java_client/service/local/AppiumDriverLocalService.java index 37851fd4b..ac86cf91b 100644 --- a/src/main/java/io/appium/java_client/service/local/AppiumDriverLocalService.java +++ b/src/main/java/io/appium/java_client/service/local/AppiumDriverLocalService.java @@ -23,6 +23,7 @@ import com.google.common.annotations.VisibleForTesting; +import lombok.SneakyThrows; import org.apache.commons.lang3.StringUtils; import org.openqa.selenium.net.UrlChecker; @@ -37,6 +38,7 @@ import java.io.OutputStream; import java.lang.reflect.Field; import java.net.MalformedURLException; +import java.net.URI; import java.net.URL; import java.time.Duration; import java.util.List; @@ -52,7 +54,7 @@ public final class AppiumDriverLocalService extends DriverService { - private static final String URL_MASK = "http://%s:%d/wd/hub"; + private static final String URL_MASK = "http://%s:%d/"; private static final Logger LOG = LoggerFactory.getLogger(AppiumDriverLocalService.class); private static final Pattern LOG_MESSAGE_PATTERN = Pattern.compile("^(.*)\\R"); private static final Pattern LOGGER_CONTEXT_PATTERN = Pattern.compile("^(\\[debug\\] )?\\[(.+?)\\]"); @@ -66,11 +68,14 @@ public final class AppiumDriverLocalService extends DriverService { private final ReentrantLock lock = new ReentrantLock(true); //uses "fair" thread ordering policy private final ListOutputStream stream = new ListOutputStream().add(System.out); private final URL url; + private String basePath; private CommandLine process = null; - AppiumDriverLocalService(String ipAddress, File nodeJSExec, int nodeJSPort, Duration startupTimeout, - List nodeJSArgs, Map nodeJSEnvironment) throws IOException { + AppiumDriverLocalService(String ipAddress, File nodeJSExec, + int nodeJSPort, Duration startupTimeout, + List nodeJSArgs, Map nodeJSEnvironment + ) throws IOException { super(nodeJSExec, nodeJSPort, startupTimeout, nodeJSArgs, nodeJSEnvironment); this.nodeJSExec = nodeJSExec; this.nodeJSArgs = nodeJSArgs; @@ -87,6 +92,26 @@ public static AppiumDriverLocalService buildService(AppiumServiceBuilder builder return builder.build(); } + public AppiumDriverLocalService withBasePath(String basePath) { + this.basePath = basePath; + return this; + } + + public String getBasePath() { + return this.basePath; + } + + @SneakyThrows + private static URL addSuffix(URL url, String suffix) { + return url.toURI().resolve("." + (suffix.startsWith("/") ? suffix : "/" + suffix)).toURL(); + } + + @SneakyThrows + @SuppressWarnings("SameParameterValue") + private static URL replaceHost(URL source, String oldHost, String newHost) { + return new URL(source.toString().replace(oldHost, newHost)); + } + /** * Base URL. * @@ -94,7 +119,7 @@ public static AppiumDriverLocalService buildService(AppiumServiceBuilder builder */ @Override public URL getUrl() { - return url; + return basePath == null ? url : addSuffix(url, basePath); } @Override @@ -125,7 +150,7 @@ public boolean isRunning() { private void ping(Duration timeout) throws UrlChecker.TimeoutException, MalformedURLException { // The operating system might block direct access to the universal broadcast IP address - URL status = new URL(url.toString().replace(BROADCAST_IP_ADDRESS, "127.0.0.1") + "/status"); + URL status = addSuffix(replaceHost(getUrl(), BROADCAST_IP_ADDRESS, "127.0.0.1"), "/status"); new UrlChecker().waitUntilAvailable(timeout.toMillis(), TimeUnit.MILLISECONDS, status); } diff --git a/src/main/java/io/appium/java_client/service/local/AppiumServiceBuilder.java b/src/main/java/io/appium/java_client/service/local/AppiumServiceBuilder.java index 93124226a..cf1e5ee89 100644 --- a/src/main/java/io/appium/java_client/service/local/AppiumServiceBuilder.java +++ b/src/main/java/io/appium/java_client/service/local/AppiumServiceBuilder.java @@ -25,8 +25,10 @@ import io.appium.java_client.remote.AndroidMobileCapabilityType; import io.appium.java_client.remote.MobileBrowserType; import io.appium.java_client.remote.MobileCapabilityType; +import io.appium.java_client.service.local.flags.GeneralServerFlag; import io.appium.java_client.service.local.flags.ServerArgument; +import lombok.SneakyThrows; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.SystemUtils; @@ -446,16 +448,15 @@ public AppiumServiceBuilder withLogFile(File logFile) { return super.withLogFile(logFile); } + @SneakyThrows @Override protected AppiumDriverLocalService createDriverService(File nodeJSExecutable, int nodeJSPort, Duration startupTimeout, List nodeArguments, Map nodeEnvironment) { - try { - return new AppiumDriverLocalService(ipAddress, nodeJSExecutable, nodeJSPort, startupTimeout, nodeArguments, - nodeEnvironment); - } catch (IOException e) { - throw new RuntimeException(e); - } + String basePath = serverArguments.getOrDefault( + GeneralServerFlag.BASEPATH.getArgument(), serverArguments.get("-pa")); + return new AppiumDriverLocalService(ipAddress, nodeJSExecutable, nodeJSPort, startupTimeout, nodeArguments, + nodeEnvironment).withBasePath(basePath); } } diff --git a/src/main/java/io/appium/java_client/service/local/flags/GeneralServerFlag.java b/src/main/java/io/appium/java_client/service/local/flags/GeneralServerFlag.java index 817deae05..19b69c38c 100644 --- a/src/main/java/io/appium/java_client/service/local/flags/GeneralServerFlag.java +++ b/src/main/java/io/appium/java_client/service/local/flags/GeneralServerFlag.java @@ -16,7 +16,6 @@ package io.appium.java_client.service.local.flags; - /** * Here is the list of common Appium server arguments. */ @@ -43,7 +42,10 @@ public enum GeneralServerFlag implements ServerArgument { * Pre-launch the application before allowing the first session * (Requires –app and, for Android, –app-pkg and –app-activity). * Default: false + * + * @deprecated This argument has been removed from Appium 2.0 */ + @Deprecated PRE_LAUNCH("--pre-launch"), /** * The message log level to be shown. @@ -75,14 +77,6 @@ public enum GeneralServerFlag implements ServerArgument { * --nodeconfig /abs/path/to/nodeconfig.json */ CONFIGURATION_FILE("--nodeconfig"), - /** - * IP Address of robot. Sample: --robot-address 0.0.0.0 - */ - ROBOT_ADDRESS("--robot-address"), - /** - * Port for robot. Sample: --robot-port 4242 - */ - ROBOT_PORT("--robot-port"), /** * Show info about the Appium server configuration and exit. Default: false */ @@ -140,33 +134,21 @@ public enum GeneralServerFlag implements ServerArgument { * Plugins are available with Appium as of Appium 2.0. * To activate all plugins, you can use the single string "all" as the value (e.g --plugins=all) * Default: [] - * Sample: --plugins=device-farm,images + * Sample: --use-plugins=device-farm,images */ - PLUGINS("--plugins"), + USE_PLUGINS("--use-plugins"), /** * A comma-separated list of installed driver names that should be active for this server. * All drivers will be active by default. * Default: [] - * Sample: --drivers=uiautomator2,xcuitest + * Sample: --use-drivers=uiautomator2,xcuitest */ - DRIVERS("--drivers"), + USE_DRIVERS("--use-drivers"), /** * Base path to use as the prefix for all webdriver routes running on this server. * Sample: --base-path=/wd/hub */ - BASEPATH("--base-path"), - /** - * Set the default desired client arguments for a plugin. - * Default: [] - * Sample: [ '{"images":{"foo1": "bar1", "foo2": "bar2"}}' | /path/to/pluginArgs.json ] - */ - PLUGINARGS("--plugin-args"), - /** - * Set the default desired client arguments for a driver. - * Default: [] - * Sample: [ '{"xcuitest": {"foo1": "bar1", "foo2": "bar2"}}' | /path/to/driverArgs.json ] - */ - DRIVERARGS("--driver-args"); + BASEPATH("--base-path"); private final String arg; diff --git a/src/test/java/io/appium/java_client/android/BaseAndroidTest.java b/src/test/java/io/appium/java_client/android/BaseAndroidTest.java index 997b83a23..b6a4e5421 100644 --- a/src/test/java/io/appium/java_client/android/BaseAndroidTest.java +++ b/src/test/java/io/appium/java_client/android/BaseAndroidTest.java @@ -18,8 +18,8 @@ import io.appium.java_client.android.options.UiAutomator2Options; import io.appium.java_client.service.local.AppiumDriverLocalService; -import io.appium.java_client.service.local.AppiumServerHasNotBeenStartedLocallyException; +import io.appium.java_client.service.local.AppiumServiceBuilder; import org.junit.AfterClass; import org.junit.BeforeClass; @@ -27,6 +27,8 @@ public class BaseAndroidTest { public static final String APP_ID = "io.appium.android.apis"; + protected static final int PORT = 4723; + private static AppiumDriverLocalService service; protected static AndroidDriver driver; @@ -34,12 +36,11 @@ public class BaseAndroidTest { * initialization. */ @BeforeClass public static void beforeClass() { - service = AppiumDriverLocalService.buildDefaultService(); + service = new AppiumServiceBuilder() + .withIPAddress("127.0.0.1") + .usingPort(PORT) + .build(); service.start(); - if (service == null || !service.isRunning()) { - throw new AppiumServerHasNotBeenStartedLocallyException( - "An appium server node is not started!"); - } UiAutomator2Options options = new UiAutomator2Options() .setDeviceName("Android Emulator") diff --git a/src/test/java/io/appium/java_client/ios/AppIOSTest.java b/src/test/java/io/appium/java_client/ios/AppIOSTest.java index 5aad73dfe..e84ae96fa 100644 --- a/src/test/java/io/appium/java_client/ios/AppIOSTest.java +++ b/src/test/java/io/appium/java_client/ios/AppIOSTest.java @@ -1,11 +1,9 @@ package io.appium.java_client.ios; import io.appium.java_client.ios.options.XCUITestOptions; -import io.appium.java_client.service.local.AppiumServerHasNotBeenStartedLocallyException; import org.junit.BeforeClass; import org.openqa.selenium.SessionNotCreatedException; -import java.net.URL; import java.time.Duration; import static io.appium.java_client.TestResources.testAppZip; @@ -16,22 +14,19 @@ public class AppIOSTest extends BaseIOSTest { @BeforeClass public static void beforeClass() throws Exception { - final String ip = startAppiumServer(); - - if (service == null || !service.isRunning()) { - throw new AppiumServerHasNotBeenStartedLocallyException("An appium server node is not started!"); - } + startAppiumServer(); XCUITestOptions options = new XCUITestOptions() + .setPlatformVersion(PLATFORM_VERSION) .setDeviceName(DEVICE_NAME) .setCommandTimeouts(Duration.ofSeconds(240)) .setApp(testAppZip().toAbsolutePath().toString()) .setWdaLaunchTimeout(WDA_LAUNCH_TIMEOUT); try { - driver = new IOSDriver(new URL("http://" + ip + ":" + PORT + "/wd/hub"), options); + driver = new IOSDriver(service.getUrl(), options); } catch (SessionNotCreatedException e) { options.useNewWDA(); - driver = new IOSDriver(new URL("http://" + ip + ":" + PORT + "/wd/hub"), options); + driver = new IOSDriver(service.getUrl(), options); } } } \ No newline at end of file diff --git a/src/test/java/io/appium/java_client/ios/BaseIOSTest.java b/src/test/java/io/appium/java_client/ios/BaseIOSTest.java index c887ab6f8..2f4d1f5f1 100644 --- a/src/test/java/io/appium/java_client/ios/BaseIOSTest.java +++ b/src/test/java/io/appium/java_client/ios/BaseIOSTest.java @@ -20,33 +20,33 @@ import io.appium.java_client.service.local.AppiumServiceBuilder; import org.junit.AfterClass; -import java.net.SocketException; -import java.net.UnknownHostException; import java.time.Duration; -import static io.appium.java_client.TestUtils.getLocalIp4Address; - public class BaseIOSTest { protected static AppiumDriverLocalService service; protected static IOSDriver driver; protected static final int PORT = 4723; public static final String DEVICE_NAME = System.getenv("IOS_DEVICE_NAME") != null - ? System.getenv("IOS_DEVICE_NAME") : "iPhone 12"; + ? System.getenv("IOS_DEVICE_NAME") + : "iPhone 12"; public static final String PLATFORM_VERSION = System.getenv("IOS_PLATFORM_VERSION") != null - ? System.getenv("IOS_PLATFORM_VERSION") : "14.5"; + ? System.getenv("IOS_PLATFORM_VERSION") + : "14.5"; public static final Duration WDA_LAUNCH_TIMEOUT = Duration.ofSeconds(240); /** * Starts a local server. * - * @return ip of a local host - * @throws UnknownHostException when it is impossible to get ip address of a local host + * @return service instance */ - public static String startAppiumServer() throws UnknownHostException, SocketException { - service = new AppiumServiceBuilder().usingPort(PORT).build(); + public static AppiumDriverLocalService startAppiumServer() { + service = new AppiumServiceBuilder() + .withIPAddress("127.0.0.1") + .usingPort(PORT) + .build(); service.start(); - return getLocalIp4Address(); + return service; } /** diff --git a/src/test/java/io/appium/java_client/ios/BaseIOSWebViewTest.java b/src/test/java/io/appium/java_client/ios/BaseIOSWebViewTest.java index 74b1c8f62..7aed47831 100644 --- a/src/test/java/io/appium/java_client/ios/BaseIOSWebViewTest.java +++ b/src/test/java/io/appium/java_client/ios/BaseIOSWebViewTest.java @@ -17,13 +17,10 @@ package io.appium.java_client.ios; import io.appium.java_client.ios.options.XCUITestOptions; -import io.appium.java_client.service.local.AppiumServerHasNotBeenStartedLocallyException; import org.junit.BeforeClass; import org.openqa.selenium.SessionNotCreatedException; import java.io.IOException; -import java.net.MalformedURLException; -import java.net.URL; import java.time.Duration; import java.util.function.Supplier; @@ -35,24 +32,14 @@ public class BaseIOSWebViewTest extends BaseIOSTest { @BeforeClass public static void beforeClass() throws IOException { - final String ip = startAppiumServer(); - - if (service == null || !service.isRunning()) { - throw new AppiumServerHasNotBeenStartedLocallyException("An appium server node is not started!"); - } + startAppiumServer(); XCUITestOptions options = new XCUITestOptions() .setDeviceName(DEVICE_NAME) .setWdaLaunchTimeout(WDA_LAUNCH_TIMEOUT) .setCommandTimeouts(Duration.ofSeconds(240)) .setApp(vodQaAppZip().toAbsolutePath().toString()); - Supplier createDriver = () -> { - try { - return new IOSDriver(new URL("http://" + ip + ":" + PORT + "/wd/hub"), options); - } catch (MalformedURLException e) { - throw new RuntimeException(e); - } - }; + Supplier createDriver = () -> new IOSDriver(service.getUrl(), options); try { driver = createDriver.get(); } catch (SessionNotCreatedException e) { diff --git a/src/test/java/io/appium/java_client/ios/BaseSafariTest.java b/src/test/java/io/appium/java_client/ios/BaseSafariTest.java index 0dd44f2a6..4204cff41 100644 --- a/src/test/java/io/appium/java_client/ios/BaseSafariTest.java +++ b/src/test/java/io/appium/java_client/ios/BaseSafariTest.java @@ -18,26 +18,20 @@ import io.appium.java_client.ios.options.XCUITestOptions; import io.appium.java_client.remote.MobileBrowserType; -import io.appium.java_client.service.local.AppiumServerHasNotBeenStartedLocallyException; import org.junit.BeforeClass; import java.io.IOException; -import java.net.URL; public class BaseSafariTest extends BaseIOSTest { @BeforeClass public static void beforeClass() throws IOException { - final String ip = startAppiumServer(); - - if (service == null || !service.isRunning()) { - throw new AppiumServerHasNotBeenStartedLocallyException("An appium server node is not started!"); - } + startAppiumServer(); XCUITestOptions options = new XCUITestOptions() .withBrowserName(MobileBrowserType.SAFARI) .setDeviceName(DEVICE_NAME) .setPlatformVersion(PLATFORM_VERSION) .setWdaLaunchTimeout(WDA_LAUNCH_TIMEOUT); - driver = new IOSDriver(new URL("http://" + ip + ":" + PORT + "/wd/hub"), options); + driver = new IOSDriver(service.getUrl(), options); } } diff --git a/src/test/java/io/appium/java_client/service/local/ServerBuilderTest.java b/src/test/java/io/appium/java_client/service/local/ServerBuilderTest.java index ea8a7c706..7db741cdd 100644 --- a/src/test/java/io/appium/java_client/service/local/ServerBuilderTest.java +++ b/src/test/java/io/appium/java_client/service/local/ServerBuilderTest.java @@ -5,7 +5,6 @@ import static io.appium.java_client.service.local.AppiumDriverLocalService.buildDefaultService; import static io.appium.java_client.service.local.AppiumServiceBuilder.APPIUM_PATH; import static io.appium.java_client.service.local.flags.GeneralServerFlag.CALLBACK_ADDRESS; -import static io.appium.java_client.service.local.flags.GeneralServerFlag.PRE_LAUNCH; import static io.appium.java_client.service.local.flags.GeneralServerFlag.SESSION_OVERRIDE; import static io.github.bonigarcia.wdm.WebDriverManager.chromedriver; import static java.lang.System.getProperty; @@ -141,7 +140,6 @@ public void checkAbilityToStartServiceUsingFlags() { service = new AppiumServiceBuilder() .withArgument(CALLBACK_ADDRESS, testIP) .withArgument(SESSION_OVERRIDE) - .withArgument(PRE_LAUNCH) .build(); service.start(); assertTrue(service.isRunning()); @@ -183,7 +181,6 @@ public void checkAbilityToStartServiceUsingCapabilitiesAndFlags() { service = new AppiumServiceBuilder() .withArgument(CALLBACK_ADDRESS, testIP) .withArgument(SESSION_OVERRIDE) - .withArgument(PRE_LAUNCH) .withCapabilities(options).build(); service.start(); assertTrue(service.isRunning()); @@ -247,7 +244,7 @@ public void checkAbilityToStartServiceWithLogFile() throws Exception { @Test public void checkAbilityToStartServiceWithPortUsingFlag() { String port = "8996"; - String expectedUrl = String.format("http://0.0.0.0:%s/wd/hub", port); + String expectedUrl = String.format("http://0.0.0.0:%s/", port); service = new AppiumServiceBuilder() .withArgument(() -> "--port", port) @@ -260,7 +257,7 @@ public void checkAbilityToStartServiceWithPortUsingFlag() { @Test public void checkAbilityToStartServiceWithPortUsingShortFlag() { String port = "8996"; - String expectedUrl = String.format("http://0.0.0.0:%s/wd/hub", port); + String expectedUrl = String.format("http://0.0.0.0:%s/", port); service = new AppiumServiceBuilder() .withArgument(() -> "-p", port) @@ -272,7 +269,7 @@ public void checkAbilityToStartServiceWithPortUsingShortFlag() { @Test public void checkAbilityToStartServiceWithIpUsingFlag() { - String expectedUrl = String.format("http://%s:4723/wd/hub", testIP); + String expectedUrl = String.format("http://%s:4723/", testIP); service = new AppiumServiceBuilder() .withArgument(() -> "--address", testIP) @@ -284,7 +281,7 @@ public void checkAbilityToStartServiceWithIpUsingFlag() { @Test public void checkAbilityToStartServiceWithIpUsingShortFlag() { - String expectedUrl = String.format("http://%s:4723/wd/hub", testIP); + String expectedUrl = String.format("http://%s:4723/", testIP); service = new AppiumServiceBuilder() .withArgument(() -> "-a", testIP)