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 d2b4d37fc..08f289289 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 @@ -35,6 +35,7 @@ import java.net.MalformedURLException; import java.net.URL; import java.time.Duration; +import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Optional; @@ -46,7 +47,8 @@ import java.util.regex.Pattern; import static com.google.common.base.Preconditions.checkNotNull; -import static io.appium.java_client.service.local.AppiumServiceBuilder.BROADCAST_IP_ADDRESS; +import static io.appium.java_client.service.local.AppiumServiceBuilder.BROADCAST_IP4_ADDRESS; +import static io.appium.java_client.service.local.AppiumServiceBuilder.BROADCAST_IP6_ADDRESS; import static org.slf4j.event.Level.DEBUG; import static org.slf4j.event.Level.INFO; @@ -57,6 +59,7 @@ public final class AppiumDriverLocalService extends DriverService { private static final Pattern LOGGER_CONTEXT_PATTERN = Pattern.compile("^(\\[debug\\] )?\\[(.+?)\\]"); private static final String APPIUM_SERVICE_SLF4J_LOGGER_PREFIX = "appium.service"; private static final Duration DESTROY_TIMEOUT = Duration.ofSeconds(60); + private static final Duration IS_RUNNING_PING_TIMEOUT = Duration.ofMillis(1500); private final File nodeJSExec; private final List nodeJSArgs; @@ -106,7 +109,7 @@ private static URL addSuffix(URL url, String suffix) { @SneakyThrows @SuppressWarnings("SameParameterValue") private static URL replaceHost(URL source, String oldHost, String newHost) { - return new URL(source.toString().replace(oldHost, newHost)); + return new URL(source.toString().replaceFirst(oldHost, newHost)); } /** @@ -128,7 +131,7 @@ public boolean isRunning() { } try { - ping(Duration.ofMillis(1500)); + ping(IS_RUNNING_PING_TIMEOUT); return true; } catch (UrlChecker.TimeoutException e) { return false; @@ -142,8 +145,15 @@ 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 = addSuffix(replaceHost(getUrl(), BROADCAST_IP_ADDRESS, "127.0.0.1"), "/status"); + URL url = getUrl(); + String host = url.getHost(); + // The operating system will block direct access to the universal broadcast IP address + if (host.equals(BROADCAST_IP4_ADDRESS)) { + url = replaceHost(url, BROADCAST_IP4_ADDRESS, "127.0.0.1"); + } else if (host.equals(BROADCAST_IP6_ADDRESS)) { + url = replaceHost(url, BROADCAST_IP6_ADDRESS, "::1"); + } + URL status = addSuffix(url, "/status"); new UrlChecker().waitUntilAvailable(timeout.toMillis(), TimeUnit.MILLISECONDS, status); } @@ -161,25 +171,36 @@ public void start() throws AppiumServerHasNotBeenStartedLocallyException { } try { - process = new CommandLine(this.nodeJSExec.getCanonicalPath(), - nodeJSArgs.toArray(new String[]{})); + process = new CommandLine( + this.nodeJSExec.getCanonicalPath(), + nodeJSArgs.toArray(new String[]{}) + ); process.setEnvironmentVariables(nodeJSEnvironment); process.copyOutputTo(stream); process.executeAsync(); ping(startupTimeout); - } catch (Throwable e) { + } catch (Exception e) { + final Optional output = Optional.ofNullable(process) + .map(CommandLine::getStdOut) + .filter((o) -> !StringUtils.isBlank(o)); destroyProcess(); - String msgTxt = "The local appium server has not been started. " - + "The given Node.js executable: " + this.nodeJSExec.getAbsolutePath() - + " Arguments: " + nodeJSArgs.toString() + " " + "\n"; - if (process != null) { - String processStream = process.getStdOut(); - if (!StringUtils.isBlank(processStream)) { - msgTxt = msgTxt + "Process output: " + processStream + "\n"; - } + List errorLines = new ArrayList<>(); + errorLines.add("The local appium server has not been started"); + errorLines.add(String.format("Reason: %s", e.getMessage())); + if (e instanceof UrlChecker.TimeoutException) { + errorLines.add(String.format( + "Consider increasing the server startup timeout value (currently %sms)", + startupTimeout.toMillis() + )); } - - throw new AppiumServerHasNotBeenStartedLocallyException(msgTxt, e); + errorLines.add( + String.format("Node.js executable path: %s", nodeJSExec.getAbsolutePath()) + ); + errorLines.add(String.format("Arguments: %s", nodeJSArgs)); + output.ifPresent((o) -> errorLines.add(String.format("Output: %s", o))); + throw new AppiumServerHasNotBeenStartedLocallyException( + StringUtils.joinWith("\n", errorLines), e + ); } } finally { lock.unlock(); 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 9d3bce564..0e354d441 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 @@ -29,7 +29,6 @@ import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.SystemUtils; -import org.apache.commons.validator.routines.InetAddressValidator; import org.openqa.selenium.Capabilities; import org.openqa.selenium.Platform; import org.openqa.selenium.os.ExecutableFinder; @@ -72,13 +71,14 @@ public final class AppiumServiceBuilder */ private static final String NODE_PATH = "NODE_BINARY_PATH"; - public static final String BROADCAST_IP_ADDRESS = "0.0.0.0"; + public static final String BROADCAST_IP4_ADDRESS = "0.0.0.0"; + public static final String BROADCAST_IP6_ADDRESS = "::"; private static final Path APPIUM_PATH_SUFFIX = Paths.get("appium", "build", "lib", "main.js"); public static final int DEFAULT_APPIUM_PORT = 4723; private final Map serverArguments = new HashMap<>(); private File appiumJS; private File node; - private String ipAddress = BROADCAST_IP_ADDRESS; + private String ipAddress = BROADCAST_IP4_ADDRESS; private Capabilities capabilities; private boolean autoQuoteCapabilitiesOnWindows = false; private static final Function APPIUM_JS_NOT_EXIST_ERROR = (fullPath) -> String.format( @@ -363,14 +363,7 @@ protected ImmutableList createArgs() { argList.add(String.valueOf(getPort())); if (StringUtils.isBlank(ipAddress)) { - ipAddress = BROADCAST_IP_ADDRESS; - } else { - InetAddressValidator validator = InetAddressValidator.getInstance(); - if (!validator.isValid(ipAddress) && !validator.isValidInet4Address(ipAddress) - && !validator.isValidInet6Address(ipAddress)) { - throw new IllegalArgumentException( - "The invalid IP address " + ipAddress + " is defined"); - } + ipAddress = BROADCAST_IP4_ADDRESS; } argList.add("--address"); argList.add(ipAddress); 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 5d4fd5715..c8d9dd0f9 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 @@ -19,7 +19,7 @@ import static io.appium.java_client.TestUtils.getLocalIp4Address; 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.AppiumServiceBuilder.BROADCAST_IP_ADDRESS; +import static io.appium.java_client.service.local.AppiumServiceBuilder.BROADCAST_IP4_ADDRESS; import static io.appium.java_client.service.local.AppiumServiceBuilder.DEFAULT_APPIUM_PORT; import static io.appium.java_client.service.local.flags.GeneralServerFlag.BASEPATH; import static io.appium.java_client.service.local.flags.GeneralServerFlag.CALLBACK_ADDRESS; @@ -323,7 +323,7 @@ void checkAbilityToStartServiceUsingValidBasePathWithMultiplePathParams() { service = new AppiumServiceBuilder().withArgument(BASEPATH, basePath).build(); service.start(); assertTrue(service.isRunning()); - String baseUrl = String.format("http://%s:%d/", BROADCAST_IP_ADDRESS, DEFAULT_APPIUM_PORT); + String baseUrl = String.format("http://%s:%d/", BROADCAST_IP4_ADDRESS, DEFAULT_APPIUM_PORT); assertEquals(baseUrl + basePath + "/", service.getUrl().toString()); } @@ -333,7 +333,7 @@ void checkAbilityToStartServiceUsingValidBasePathWithSinglePathParams() { service = new AppiumServiceBuilder().withArgument(BASEPATH, basePath).build(); service.start(); assertTrue(service.isRunning()); - String baseUrl = String.format("http://%s:%d/", BROADCAST_IP_ADDRESS, DEFAULT_APPIUM_PORT); + String baseUrl = String.format("http://%s:%d/", BROADCAST_IP4_ADDRESS, DEFAULT_APPIUM_PORT); assertEquals(baseUrl + basePath.substring(1), service.getUrl().toString()); }