Monday, October 12, 2015

Start and Stop Appium Server Programmatically

Pre-requisites:
I assume you have installed the followings in your machine already.
Appium is downloaded (latest from here) and setup properly as mentioned here.
Java Development Kit(JDK) is downloaded(refer here) and installed in your machine.

How to Start or Stop appium server programmatically?
Please refer the below class for detailed usage, Hope it is self explanatory.

Method-1:
package appium.base;

import java.io.File;

import io.appium.java_client.service.local.AppiumDriverLocalService;
import io.appium.java_client.service.local.AppiumServiceBuilder;

/**
 * This page models Appium server
 * 
 * @author A. K. Sahu
 *
 */
public class AppiumServer {

 String appiumInstallationDir = "C:/Program Files (x86)";// e.g. in Windows
 //String appiumInstallationDir = "/Applications";// e.g. for Mac
 AppiumDriverLocalService service = null;

 public AppiumServer() {
  File classPathRoot = new File(System.getProperty("user.dir"));
  String osName = System.getProperty("os.name");

  if (osName.contains("Windows")) {
   service = AppiumDriverLocalService.buildService(new AppiumServiceBuilder()
     .usingDriverExecutable(new File(appiumInstallationDir + File.separator + "Appium" + File.separator + "node.exe"))
     .withAppiumJS(new File(appiumInstallationDir + File.separator + "Appium" + File.separator
       + "node_modules" + File.separator + "appium" + File.separator + "bin" + File.separator + "appium.js"))
     .withLogFile(new File(new File(classPathRoot, File.separator + "log"), "androidLog.txt")));

  } else if (osName.contains("Mac")) {
   service = AppiumDriverLocalService.buildService(new AppiumServiceBuilder()
     .usingDriverExecutable(new File(appiumInstallationDir + "/Appium.app/Contents/Resources/node/bin/node"))
     .withAppiumJS(new File(
       appiumInstallationDir + "/Appium.app/Contents/Resources/node_modules/appium/bin/appium.js"))
     .withLogFile(new File(new File(classPathRoot, File.separator + "log"), "androidLog.txt")));

  } else {
   // you can add for other OS, just to track added a fail message
   Assert.fail("Starting appium is not supporting the current OS.");
  }
 }

 /**
  * Starts appium server
  */
 public void startAppiumServer() {
  service.start();
 }

 /**
  * Stops appium server
  */
 public void stopAppiumServer() {
  service.stop();
 }
}

Usage of this class:
AppiumServer service = new AppiumServer();

// Starts appium server
service.startAppiumServer();

// ... your tests are here. 

// Stops appium server
service.stopAppiumServer();
Note: You can call start appium server in the @BeforeSuite configuration method and stop appium server at @AfterSuite configuration methods.

With the above approach of starting appium server, you can get total android log in the provided log file i.e. "androidlog.txt" file inside "log" directory of your project root directory

Method-2
Here is an another way of starting and killing appium server programmatically using command line executions with java's ProcessBuilder class. Refer below class for more details.
Define the below properties and change as per your appium installation.
 String appiumInstallationDir = "C:/Program Files (x86)";
 String appiumNode = appiumInstallationDir + File.separator + "Appium" + File.separator + "node.exe";
 String appiumNodeModule = appiumInstallationDir + File.separator + "Appium" + File.separator + "node_modules"
   + File.separator + "appium" + File.separator + "bin" + File.separator + "Appium.js";
 String appiumServicePort = "4723";
Execute the start and stop commands like we do from command line but here with using java's ProcessBuilder class.
 /**
  * Starts appium server
  */
 public void startAppiumServer() {
  executeCommand("\"" + appiumNode + "\" \"" + appiumNodeModule 
                      + "\" " + "--no-reset --local-timezone");
 }

 /**
  * Stops appium server
  */
 public void stopAppiumServer() {
 executeCommand("cmd /c echo off & FOR /F \"usebackq tokens=5\" %a in" 
  + " (`netstat -nao ^| findstr /R /C:\""
  + appiumServicePort + "\"`) do (FOR /F \"usebackq\" %b in"
  + " (`TASKLIST /FI \"PID eq %a\" ^| findstr /I node.exe`) do taskkill /F /PID %a)");
}
You may need the below class for executing a command line command. This you can do using Runtime.getRuntime().exec(command) also, but I would suggest to use ProcssBuilder as this is recommended.
 /**
  * Executes any command for Windows using ProcessBuilder of Java You can
  * change the first input parameter of ProcessBuilder constructor if your OS
  * is not windows operating system
  * 
  * @param aCommand
  */
 public void executeCommand(String aCommand) {
  File currDir = new File(System.getProperty("user.dir"));
  String line;
  try {
   ProcessBuilder probuilder = new ProcessBuilder("CMD", "/C", aCommand);
   probuilder.directory(currDir);
   Process process = probuilder.start();

   BufferedReader inputStream 
        = new BufferedReader(new InputStreamReader(process.getInputStream()));
   BufferedReader errorStream 
        = new BufferedReader(new InputStreamReader(process.getErrorStream()));

   // reading output of the command
   int inputLine = 0;
   while ((line = inputStream.readLine()) != null) {
    if (inputLine == 0) {
     System.out.printf("Output of the running command is: \n");
    }
    System.out.println(line);
    inputLine++;
   }

   // reading errors from the command
   int errLine = 0;
   while ((line = errorStream.readLine()) != null) {
    if (errLine == 0) {
     System.out.println("Error of the command is: \n");
    }
    System.out.println(line);
    errLine++;
   }

  } catch (IOException e) {
   System.err.println("Exception occured: \n");
   System.err.println(e.getMessage());
  }
 }

With the above method, the server log will be printed in the console as I kept standard output and error statements. You can change to trace these log in a log file if you need it.

Hope it is useful !!

28 comments:

  1. Thanks Sahu for this post , It helped a lot . However i would like to know how we can add -U argument to the first approach using AppiumDriverLocalService .

    cheers

    ReplyDelete
  2. oh I think withArgument(GeneralServerFlag.DEVICE_NAME,"") solved my problem . Anyways thanks a ton :)

    ReplyDelete
  3. A Little help needed here :) I've started 2 sessions using your first method for multi device testing , Aim is to execute same test in two device one after another using single script , but even though i am starting another session for my second device test is executing only on my first device "Two times" ..What am i doing wrong ?

    Any help appreciated :)
    Cheers ,

    ReplyDelete
  4. oh yeah !!! withArgument(GeneralServerFlag.DEVICE_NAME,"") should have been withArgument(GeneralServerFlag.UIID,"") . and It worked !

    ReplyDelete
    Replies
    1. Nice! Before I get a chance to answer you, you posted your solution :) Good that few of the possible questions you have already discussed here which will be helpful to other readers too.

      Cheers!

      Delete
    2. Thank you so much!!!! I struggled to start Appium server from java. Thank you for your help!!!!!! Marina

      Delete
  5. Hi,

    Nice one.
    But when I tried to replicate the same, getting following error.

    java.lang.NoClassDefFoundError: org/apache/commons/validator/routines/InetAddressValidator
    at io.appium.java_client.service.local.AppiumServiceBuilder.createArgs(AppiumServiceBuilder.java:230)
    at org.openqa.selenium.remote.service.DriverService$Builder.build(DriverService.java:293)

    Code:

    AppiumDriverLocalService service = AppiumDriverLocalService
    .buildService(new AppiumServiceBuilder()
    .usingDriverExecutable(new File("C:"+File.separator+"Program Files"+File.separator+"nodejs/node.exe"))
    .withAppiumJS(new File("C:/Program Files/Appium/node_modules/appium/bin/appium.js"))
    .withLogFile(new File("C:/Appium/log/appiumLogs.txt"))
    .withIPAddress("127.0.0.1").usingPort(4723));
    service.start();

    }

    Can you please let me know the possible cause for that. Please help

    Thanks

    ReplyDelete
    Replies
    1. Hi Nitesh,

      This is caused when there is a class file that your code depends on and it is present at compile time but not found at runtime. Look for differences in your build time and runtime classpaths then your problem will resolve.

      Few more things you may double check:
      - Make sure your appium installation directory is correct and used same in the above code
      - I am assuming you are using windows m/c, otherwise change the path of appium directory accordingly
      - If you are not using any IDE to build and trying to do this from command line then please set the classpath correctly, you may refer https://en.wikipedia.org/wiki/Classpath_%28Java%29 for how to set it.

      Hope it will resolve your issue.

      Delete
    2. Hi Nitesh,

      Even I got the same exception while try to run my test script using testNG but after adding the Apache commons-validator-1.5.0 jar in the build class path, my problem was solved. You can try the same!!!

      Thanks Aswini Kumar Sahu for posting a fantastic information.

      Delete
  6. Thanks for sharing this Information, Got to learn new things from your Blog on APPIUM.
    Ref link : http://thecreatingexperts.com/appium-training-in-chennai/

    ReplyDelete
  7. Thanks for sharing this information.It was very nice blog to learn about Appium.
    http://thecreatingexperts.com/appium-training-in-chennai/

    ReplyDelete
  8. Hi All,

    I tried using first 'Method-1', but got the same error as Nitiesh mentioned. Am using version of Appium (1.4.16.1), Java-Client (4.0.0) and Java 8. Please advice me. I would highly appreciate your quick response. Thanks!

    "Exception in thread "main" java.lang.NoClassDefFoundError: org/apache/commons/lang3/StringUtils"

    Regards,
    Sakthi

    ReplyDelete
    Replies
    1. Hi Sakthi,

      Please ensure you have added required libraries and jar files in the project build path.
      Looks like you have missed to add commons-langx-x.x.jar or some other jar that you are using the "org.apache.commons.lang3.StringUtils" class. Please add it to your build path, it should fix your issue.

      Thanks,
      Aswini Kumar

      Delete
  9. How can suppress printing logs on console?

    ReplyDelete
  10. Hey I got below error while following the above approach:

    /usr/local/lib/node_modules/appium/bin/appium.js:1
    (function (exports, require, module, __filename, __dirname) { import _ from 'lodash’;
    ^^^^^^
    SyntaxError: Unexpected token import
    at Object.exports.runInThisContext (vm.js:53:16)
    at Module._compile (module.js:513:28)
    at Object.Module._extensions..js (module.js:550:10)
    at Module.load (module.js:458:32)
    at tryModuleLoad (module.js:417:12)
    at Function.Module._load (module.js:409:3)
    at Function.Module.runMain (module.js:575:10)
    at startup (node.js:160:18)
    at node.js:449:3

    ReplyDelete
  11. Hi Sahu, Thank you for the post and it is much useful. However I am using method 1 and encountering following error when used through eclipse to start appium:

    java.lang.NullPointerException
    at java.io.File.(File.java:277)
    at Objectrepository.AppiumServer.(AppiumServer.java:17)
    at Testcases.SinglebagApplication.Singlebag(SinglebagApplication.java:32)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
    at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
    at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:86)
    at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:459)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:675)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:382)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:192)

    ReplyDelete
  12. Hi Sahu,

    Appium server launches every time whenever new scenario starts while using Cucumber-Selenium.
    Please do help in resolving this issue.It will be very helpful!!!Thanks in Advance :)

    ReplyDelete
    Replies
    1. May be you are calling the server start function at the wrong place. Please check that.

      Delete
    2. Hey Murali - Were you able to resolve this? I am also facing the same issue.

      Delete
  13. Hi,
    i'm missing the AppiumDriverLocalService, am i missing some jar file?
    i also tried running with the second method and the command "Process process = probuilder.start();" fails for no reason (can't debug it...).
    please help ..

    ReplyDelete
    Replies
    1. Can you provide the error trace? (may be for the both methods, if you want)

      Delete
    2. Hi,
      there is no eror trace simply because it ran and "finished", just didn't do anything. i succeeded to run it using the second method, but still can't run using the first method because of AppiumDriverLocalService - it just does not compile and marks it as a wrong argument. i need to run it on ios too, is there a way to do it using the second method?

      Delete
    3. Probably, some class path or installation issue. Without the error trace or error info, its tough to guess the problem. If you can tell me what's the compilation error says also I can help. You can ping/mail me personally if you want

      Delete
    4. There still any error trace. anyway, i figured out a way to send command to Terminal in mac. thanks for your help and great blog!

      Delete
  14. Hi, Method 1 worked fine with me. Thanks a ton for this code. But I've 2 other concerns
    1. I'm running my tests as maven project, hence i need appium to be executed at backened, I mean when I'm running "mvn test" from command prompt, appium logs are also getting displayed in cmd prompt. Can this be avoided ??

    2. Time displayed in androidLog.txt is not in sync with GMT. How can i change these settings.

    ReplyDelete
  15. Very Helpful thank you

    ReplyDelete