appium/java-client

When building a AppiumDriverLocalService, there is a Port number missmatch when providing --port argument

alexander-poulikakos opened this issue · 8 comments

Description

When providing the port number via withArgument("--port", "8998") method, there becomes a miss match in ports, i.e. server starts on one port, while java_client tries to connect to another port. In below code example/stackTrace The Appium server is started on port 8998, but the java_client tries to connect to the default port "4723" and times out with an exception.

As can be seen in below stacktrace the port number is added twice in the node arguments.
The given Node.js executable: /usr/local/Cellar/node/5.5.0/bin/node Arguments: [/usr/local/lib/node_modules/appium/build/lib/main.js, --port, 4723, --address, 0.0.0.0, --port, 8998]

Environment

  • java client build version: java_client-4.1.2
  • Appium server version: v1.6.1-beta
  • Desktop OS/version used: MAC OSX 10.11.6
  • Node.js version: v5.5.0
  • Mobile platform/version under test:
  • Real device

Code To Reproduce Issue [ Good To Have ]

package _play;

import java.util.HashMap;
import java.util.Map;

import org.openqa.selenium.remote.DesiredCapabilities;

import io.appium.java_client.ios.IOSDriver;
import io.appium.java_client.remote.MobileCapabilityType;
import io.appium.java_client.service.local.AppiumDriverLocalService;
import io.appium.java_client.service.local.AppiumServiceBuilder;

public class _AppiumServerClientPlay {
	
	public static void main(String[] args) {
		Map<String, String> env = new HashMap<>(System.getenv());
		env.put("PATH", "/usr/local/bin:" + env.get("PATH"));
		
		AppiumDriverLocalService server = new AppiumServiceBuilder()
		.withEnvironment(env)
		.withArgument(() -> "--port", "8998")
		.build();
		
		DesiredCapabilities caps = new DesiredCapabilities();
		caps.setCapability(MobileCapabilityType.APP, "__/path/to/your.app__");
		caps.setCapability(MobileCapabilityType.UDID, "__your_UDID_here__");
		caps.setCapability(MobileCapabilityType.DEVICE_NAME, "Iphone");
		
		new IOSDriver<>(server, caps);
	}
}

Ecxeption stacktraces

[Appium] Welcome to Appium v1.6.1-beta
[Appium] Non-default server args:
[Appium]   port: 8998
[Appium] Appium REST http interface listener started on 0.0.0.0:8998
Exception in thread "main" org.openqa.selenium.remote.UnreachableBrowserException: Could not start a new session. Possible causes are invalid address of the remote server or browser start-up failure.
Build info: version: '2.53.1', revision: 'a36b8b1cd5757287168e54b817830adce9b0158d', time: '2016-06-30 19:26:09'
System info: host: '******', ip: '*********', os.name: 'Mac OS X', os.arch: 'x86_64', os.version: '10.11.6', java.version: '1.8.0_72'
Driver info: driver.version: IOSDriver
	at org.openqa.selenium.remote.RemoteWebDriver.execute(RemoteWebDriver.java:665)
	at io.appium.java_client.DefaultGenericMobileDriver.execute(DefaultGenericMobileDriver.java:40)
	at io.appium.java_client.AppiumDriver.execute(AppiumDriver.java:1)
	at io.appium.java_client.ios.IOSDriver.execute(IOSDriver.java:1)
	at org.openqa.selenium.remote.RemoteWebDriver.startSession(RemoteWebDriver.java:249)
	at org.openqa.selenium.remote.RemoteWebDriver.<init>(RemoteWebDriver.java:131)
	at org.openqa.selenium.remote.RemoteWebDriver.<init>(RemoteWebDriver.java:144)
	at io.appium.java_client.DefaultGenericMobileDriver.<init>(DefaultGenericMobileDriver.java:36)
	at io.appium.java_client.AppiumDriver.<init>(AppiumDriver.java:114)
	at io.appium.java_client.AppiumDriver.<init>(AppiumDriver.java:145)
	at io.appium.java_client.ios.IOSDriver.<init>(IOSDriver.java:108)
	at _play._AppiumServerClientPlay.main(_AppiumServerClientPlay.java:32)
Caused by: io.appium.java_client.service.local.AppiumServerHasNotBeenStartedLocallyException: The local appium server has not been started. The given Node.js executable: /usr/local/Cellar/node/5.5.0/bin/node Arguments: [/usr/local/lib/node_modules/appium/build/lib/main.js, --port, 4723, --address, 0.0.0.0, --port, 8998] 
Process output: [Appium] Welcome to Appium v1.6.1-beta
[Appium] Non-default server args:
[Appium]   port: 8998
[Appium] Appium REST http interface listener started on 0.0.0.0:8998


	at io.appium.java_client.service.local.AppiumDriverLocalService.start(AppiumDriverLocalService.java:155)
	at io.appium.java_client.remote.AppiumCommandExecutor.execute(AppiumCommandExecutor.java:65)
	at org.openqa.selenium.remote.RemoteWebDriver.execute(RemoteWebDriver.java:644)
	... 11 more
Caused by: org.openqa.selenium.net.UrlChecker$TimeoutException: Timed out waiting for [http://0.0.0.0:4723/wd/hub/status] to be available after 120003 ms
	at org.openqa.selenium.net.UrlChecker.waitUntilAvailable(UrlChecker.java:107)
	at io.appium.java_client.service.local.AppiumDriverLocalService.ping(AppiumDriverLocalService.java:115)
	at io.appium.java_client.service.local.AppiumDriverLocalService.start(AppiumDriverLocalService.java:142)
	... 13 more
Caused by: com.google.common.util.concurrent.UncheckedTimeoutException: java.util.concurrent.TimeoutException
	at com.google.common.util.concurrent.SimpleTimeLimiter.callWithTimeout(SimpleTimeLimiter.java:143)
	at org.openqa.selenium.net.UrlChecker.waitUntilAvailable(UrlChecker.java:80)
	... 15 more
Caused by: java.util.concurrent.TimeoutException
	at java.util.concurrent.FutureTask.get(FutureTask.java:205)
	at com.google.common.util.concurrent.SimpleTimeLimiter.callWithTimeout(SimpleTimeLimiter.java:130)
	... 16 more

It seems as these server flags gets a special treatment: --log, --port and --address. I think the withArgument(...) method should be able to handle those as well. The usecase in my case is I'm reading the appium server flags from a properties file and would prefer to treat them in the same generic way.

Would modifying the withArgument(...) method to something like the below proposal be ok? If so, I can provide a pull request (with unit tests etc.) and we can continue discussing it from there.

    /**
     * @param argument is an instance which contains the argument name.
     * @param value    A non null string value. (Warn!!!) Boolean arguments have a special moment:
     *                 the presence of an arguments means "true". At this case an empty string
     *                 should be defined.
     * @return the self-reference.
     */
	public AppiumServiceBuilder withArgument(ServerArgument argument, String value) {
		String argName = argument.getArgument().trim();
		if (argName.equals("--port") || argName.equals("-p")) {
			usingPort(Integer.valueOf(value));
		} else if (argName.equals("--address") || argName.equals("-a")) {
			withIPAddress(value);
		} else if (argName.equals("--log") || argName.equals("-g")) {
			withLogFile(new File(value));
		} else {
			serverArguments.put(argName, value);
		}
		return this;
    }

@alexander-poulikakos
Please just use these methods

usingPort()
withIPAddress()
withLogFile()

Thse is the set of methods provided by DriverService (except withIPAddress)

As I can read the log there is another problem

[Appium] Welcome to Appium v1.6.1-beta
[Appium] Non-default server args:
[Appium]   port: 8998
[Appium] Appium REST http interface listener started on 0.0.0.0:8998

That means that the service was started successfully (!!!).
But

aused by: org.openqa.selenium.net.UrlChecker$TimeoutException: Timed out waiting for [http://0.0.0.0:4723/wd/hub/status] to be available after 120003 ms
	at org.openqa.selenium.net.UrlChecker.waitUntilAvailable(UrlChecker.java:107)

Do you see the difference? This is because it considers that the port was not provided so it uses the default value (4723)

Actually current implementation does same things as you are going to propose. It adds port, log file and ip to arguments.

thanks for your reply @TikhomirovSergey.

My use case is that I am reading the arguments from a properties file, something like this:

argument.--port = 8998
argument.--log-level = info
argument.--log = /path/to/log
argument.--log-timestamp=

and I would like to set them in a generic way for all arguments. Not make special cases for some of the arguments.

Since the API allows any argument to be set through the ´withArgument(...)´ method, then it should work for all arguments, including --port.

For example, a unit test like below should pass. Currently it is not.

    @Test public void checkAbilityToStartServiceWithPortUsingFlags() throws Exception {
    	String port = "8996";
    	String expectedUrl = String.format("http://0.0.0.0:%s/wd/hub", port);
    	
    	AppiumDriverLocalService service = new AppiumServiceBuilder()
    			.withArgument(() -> "--port", port)
    			.build();
	String actualUrl = service.getUrl().toString();
	assertEquals(expectedUrl, actualUrl);
    }

The problem is that the ´DriverService´ does not get aware of the new port when using ´withArgument(() -> "--port", "...")´ method.

Actually current implementation does same things as you are going to propose. It adds port, log file and ip to arguments.

No, that is not the same thing. What I propose is to make ´DriverService´ aware of the new port when using ´withArgument(...)´ method.

@alexander-poulikakos Now I got your problem. Ok. You can propose the PR.

My remark: Please use equalsIgnoreCase instead of equals
Also don't forget to provide some tests.

It was improved. It is going to be published soon.

Just a minor comment: This calls the toLowerCase() method.

 String argName = argument.getArgument().trim().toLowerCase();

So no need to call equalsIgnoreCase multiple (6) times in below code.

        String argName = argument.getArgument().trim().toLowerCase();
        if ("--port".equalsIgnoreCase(argName) || "-p".equalsIgnoreCase(argName)) {
            usingPort(Integer.valueOf(value));
        } else if ("--address".equalsIgnoreCase(argName) || "-a".equalsIgnoreCase(argName)) {
             withIPAddress(value);
        } else if ("--log".equalsIgnoreCase(argName) || "-g".equalsIgnoreCase(argName)) {
            withLogFile(new File(value));
        } else {
            serverArguments.put(argName, value);
        }

Would be cleaner if equals is used :)

        String argName = argument.getArgument().trim().toLowerCase();
        if ("--port".equals(argName) || "-p".equals(argName)) {
            usingPort(Integer.valueOf(value));
        } else if ("--address".equals(argName) || "-a".equals(argName)) {
             withIPAddress(value);
        } else if ("--log".equals(argName) || "-g".equals(argName)) {
            withLogFile(new File(value));
        } else {
            serverArguments.put(argName, value);
        }

ok. Will improve that.

I'm closing this ticket.