Yash Yash - 3 months ago 103
Node.js Question

Appium parallel execution on Android platform with multiple devices is allowing to executing on a single device

I am unable to automate my tests parallelly in an Android platform with multiple mobile devices.

If I connect 2 devices to my system and provide capabilities like mobile device Name and version, It is executing in the device which is recognized first through

.

If try to start the appium servers with different ports,

new AppiumServiceBuilder().usingAnyFreePort() I am getting Null pointer Exception.
[36minfo[39m: Appium REST http interface listener started on 0.0.0.0:2583
[36minfo[39m: [debug] Non-default server args: {"port":2583}



1) How to override session with AppiumServiceBuilder()

2) How to Restrict appium to execute on the device which is provided with capabilities, even 2 or more devices are connected.


My Code:

public class ApiumMain {
public static void main(String[] args) {
new Thread( new Device_Thread( "4897bb00", "6.0.1" ) ).start();
// new Thread( new Device_Thread( "TA64301YVY", "5.0.1" ) ).start();
}
}
public class Device_Thread extends ApiumMain implements Runnable {

public String mobileDeviceName;
public String androidVersion;

public Device_Thread( String mobile, String version ) {
mobileDeviceName = mobile;
androidVersion = version;
}

@Override
public void run() {
String hsotMachineIP = "127.0.0.1";
Integer seleniumProt = 4723; /*Default {"port":4723}*/

String nodeJSExecutable = "C:\\Program Files (x86)\\Appium\\node.exe";
String appiumJS = "C:\\Program Files (x86)\\Appium\\node_modules\\appium\\bin\\appium.js";
Integer nodeJSPort = (int)( Math.random() * 8000 ) + 1000;

startAppium(nodeJSExecutable, nodeJSPort, appiumJS, mobileDeviceName, androidVersion, hsotMachineIP, seleniumProt);
}

public void startAppium(String nodeJSExecutable, int nodeJSPort, String appiumJS,
String mobileDeviceName, String androidVersion, String hsotMachineIP, int seleniumProt ) {

RemoteWebDriver driver = null;
String appURL = "http://www.w3schools.com/";

// http://appium.io/slate/en/master/?java#appium-server-capabilities
DesiredCapabilities capabilities = new DesiredCapabilities();
capabilities.setCapability("automationName", "Appium" );
capabilities.setCapability("platformName", "Android");
capabilities.setCapability("platformVersion", androidVersion );
capabilities.setCapability("deviceName", mobileDeviceName );
capabilities.setCapability("app", "chrome");
capabilities.setCapability("browserName", "chrome");
capabilities.setCapability("newCommandTimeout", "0");
// ANDROID ONLY
/*capabilities.setCapability("appActivity", "com.android");
capabilities.setCapability("appPackage", ".ApiumMain");
*/

// Appium servers are nothing but the Node.js server
AppiumDriverLocalService service = AppiumDriverLocalService.buildService(
new AppiumServiceBuilder()
.usingDriverExecutable( new File( nodeJSExecutable ) )
.withAppiumJS( new File( appiumJS ) )
);

service.start();
System.out.println( "Device : " + mobileDeviceName );

try {
String url = String.format("http://%s:%d/wd/hub", hsotMachineIP, seleniumProt);
System.out.println(" Server Address : " + url );
driver = new RemoteWebDriver( new URL( url ), capabilities );

driver.get( appURL );
browserActions( driver, appURL );

}catch (MalformedURLException e) {
e.printStackTrace();
} finally {
driver.quit();
service.stop();
}
}
private void browserActions(RemoteWebDriver driver, String appURL) {

try {
WebElement ele = driver.findElementByXPath("//body/div[4]/div[1]/div[1]/a[1]");
ele.click();

WebElement ele2 = driver.findElementByXPath("//*[@id='topnav']/div[1]/div[1]/a[2]");
ele2.click();

System.in.read();
System.in.read();
} catch (ElementNotVisibleException logMsg){
} catch (IOException e) {
}
}
}


Appium
setup
for Windows:

Installed Android
SDK
, Appium [
Server
&
java-client
]
, Add
ADT pugin
to Eclipse IDE and Selenium-server-
standalone


set Environment Variables:

AVA_HOME~C:\Program Files (x86)\Java\jdk1.8.0_66
ANDROID_HOME~D:\Android\sdk
Path~D:\Android\sdk;D:\Android\sdk\platform-tools - ADB Debugger

Answer

Running multiple Appium sessions with different Server flags for Parallel Android Tests

Appium is an open-source tool for automating native, mobile web, and hybrid applications on IOS and Android platforms.

Appium client libraries wraps around Selenium client libraries with some extra commands added to controlling mobile devices

  • Client: The machine on which the WebDriver[HTTP Client] API is being used to communicate with server through commands. Appium Client implements the "Mobile JSONWP" to extend "JSONWP". The Mobile JSONWP is for automation of native and hybrid mobile applications and JSONWP is for automating web-browser applications.

Commands:Each HTTP request with a method and template defined in this specification represents a single command and therefore each command produces a single HTTP response. Client initiates a session with server using desired capabilities via JSON. Based on the browser session ID and Selenium remoteServerAddress the communication takes place through Http Rest API.

Selenium Source of HTTP Client:

HttpClient.Factory httpClientFactory = new ApacheHttpClient.Factory();
HttpClient client = httpClientFactory.createClient(remoteServerAddress);

HttpRequest httpRequest = commandCodec.encode(command);
HttpResponse httpResponse = client.execute(httpRequest, true);
Response response = responseCodec.decode(httpResponse);
  • Server: Before starting any mobile tests you must start the servers like RemoteWebDriver & Appium|NodeJS. AppiumServer - REST API written in Node.js SeleniumRCServer - The Selenium server receives Selenium commands from your test program using HTTP GET/POST requests, interprets and executes those commands, and reports back the results to your program.

Connect couple of android devices to the system and run below adb shell commands

D:\Yash>adb devices
List of devices attached
TA64301YVY      device
4897bb00        device

D:\Yash>adb -s TA64301YVY shell getprop ro.build.version.release
5.1    

D:\Yash>adb -s TA64301YVY shell getprop > D:\Yash\cmdOUT.txt
        +----------------------------+------------+--------------+
        |[net.bt.name]:              | [Android]  | [Android]    |
        |[ro.boot.serialno]:         | [4897bb00] | [TA64301YVY] |
        |[ro.product.brand]:         | [samsung]  | [motorola]   |
        |[ro.product.model]:         | [SM-G900F] | [XT1052]     |
        |[ro.build.version.release]: | [6.0.1]    | [5.1]        |
        +----------------------------+------------+--------------+

Q1 - Ans: To override a session with AppiumServiceBuilder use this argument "GeneralServerFlag.SESSION_OVERRIDE". presence of an arguments means "true"

Q2 - Ans: If we are not providing UDID (OR) RemoteAddress then Driver will connect to first device which is detected by ADB. If we want to restrict appium to connect specific named physical device in the list of connected devices use UDID-Unique device identifier.

JAVA Parallel Android Tests to automate mobile web apps [java-client-4.1.1.jar]:

  • nodeJSExec « [C:\Program Files (x86)\Appium\node.exe]
  • nodeJSArgs « [C:\Program Files (x86)\Appium\node_modules\appium\bin\appium.js, --port, 1750, --address, 127.0.0.1, --log, D:\Yash\MyAppiumConsole_4897bb00.txt, --chromedriver-port, 7115, --bootstrap-port, 7119, --session-override]

public void startAppium(String nodeJSExecutable, int nodeJSPort, String appiumJS, String mobileDeviceName,
    String androidVersion, String hsotMachineIP ) {
    RemoteWebDriver driver = null;
    String appURL = "http://www.w3schools.com/";

    File logFile = new File("D:/Yash/MyAppiumConsole_"+mobileDeviceName+".txt");
    File javaConsole = new File("D:/Yash/MyJavaConsole_"+mobileDeviceName+".txt");
    systemConsole2File( javaConsole );

    /*The hub only reads browserName, version, platform and applicationName.
    The other capabilities (deviceName, platformVersion, platformName, ...) are Appium capabilities, not used by the hub.*/     

    // Proxying straight through to Chromedriver
    DesiredCapabilities capabilities = DesiredCapabilities.android();
    /* Appium - io.appium.java_client.remote */
    capabilities.setCapability(MobileCapabilityType.AUTOMATION_NAME,    "Appium" );
    capabilities.setCapability(MobileCapabilityType.PLATFORM_NAME,      "Android");
    capabilities.setCapability(MobileCapabilityType.PLATFORM_VERSION,   androidVersion );
    capabilities.setCapability(MobileCapabilityType.DEVICE_NAME,        mobileDeviceName );
    capabilities.setCapability(MobileCapabilityType.BROWSER_NAME,       "chrome"); // "Browser"
    capabilities.setCapability(MobileCapabilityType.UDID,               mobileDeviceName);

    /* Selenium - org.openqa.selenium.remote */
    capabilities.setCapability(CapabilityType.TAKES_SCREENSHOT, "true");        

    // Appium servers are nothing but the Node.js server        
    AppiumDriverLocalService service = AppiumDriverLocalService.buildService(
                new AppiumServiceBuilder()
                .withAppiumJS( new File( appiumJS ) )
                .usingDriverExecutable( new File( nodeJSExecutable ) )
                .usingPort( nodeJSPort )
                .withIPAddress( hsotMachineIP )
                .withArgument(GeneralServerFlag.SESSION_OVERRIDE)
                .withArgument(AndroidServerFlag.BOOTSTRAP_PORT_NUMBER, nextFreePort().toString())
                .withArgument(AndroidServerFlag.CHROME_DRIVER_PORT, nextFreePort().toString())
                .withLogFile(logFile)
            );      
    service.start();

    System.out.println(" Appium Server Address [Local]: " + service.getUrl() );
    driver = new RemoteWebDriver( service.getUrl(), capabilities );     
    ...
}
public static final int HIGHEST_PORT = 65535;
public static final int LEAST_PORT = 1024;
public static Integer nextFreePort() {
    Integer port = (int)( Math.random() * 8000 ) + LEAST_PORT;
    while (true) {
        try ( ServerSocket endpoint = new ServerSocket(port) ) {
            System.out.println("Local Port on which this socket is listening :"+port);
            return port;
        } catch (IOException e) {
            port = ThreadLocalRandom.current().nextInt();
        }
    }
}
public static void systemConsole2File( File logFile ) {
    try ( FileOutputStream fos = new FileOutputStream( logFile );
          PrintStream system_console = new PrintStream( fos )
        ) {
        System.setOut( system_console ); // output stream.
        System.setErr( system_console ); // error output stream.
    } catch (IOException e) { 
        e.printStackTrace(); 
    }
}

Appium log with endpoints from the node where script is executed:

<?-- APPIUM_NODEJS_LISTENING_PORT = 4723 SERVERS <--port, --callback-port 4723> 
    Mobile « http://127.0.0.1:4723/wd/hub
    Desktop « http://localhost:4444/wd/hub
-->
[36minfo[39m: Appium REST http interface listener started on 127.0.0.1:1750
[36minfo[39m: [debug] Non-default server args: 
{
    "address":"127.0.0.1",
    "port":1750,
    "bootstrapPort":7119,
    "sessionOverride":true,
    "log":"D:\\Yash\\MyConsole_TA64301YVY.txt",
    "chromeDriverPort":7115
}


<?-- LIST OF CONNECTED DEVICES [UDID]
Unique device identifier of the connected physical device
To connect specific named device in the list of connected devices use UDID
-->
[36minfo[39m: [debug] Trying to find a connected android device
[36minfo[39m: [debug] Getting connected devices...
[36minfo[39m: [debug] executing cmd: D:\Android\sdk\platform-tools\adb.exe devices
[36minfo[39m: [debug] 2 device(s) connected
[36minfo[39m: Found device TA64301YVY
[36minfo[39m: [debug] Setting device id to TA64301YVY

<?-- 
BOOTSTRAP  = 4724 Socket tcp port to use on device to talk to Appium
        « Forwarding system:4724 to device:4724   appium bootstrap to device
-->
[36minfo[39m: [debug] Forwarding system:7119 to device:4724
[36minfo[39m: [debug] executing cmd: D:\Android\sdk\platform-tools\adb.exe -s TA64301YVY forward tcp:7119 tcp:4724
[36minfo[39m: [debug] Pushing appium bootstrap to device...


<?-- 
Chrome debugger server to connect to, in the form of <hostname/ip:port>
CHROME_DRIVER  = 9515 Port upon which ChromeDriver will run
-->
[36minfo[39m: Chromedriver: Spawning chromedriver with: C:\Program Files (x86)\Appium\node_modules\appium\node_modules\appium-chromedriver\chromedriver\win\chromedriver.exe --url-base=wd/hub --port=7115
[36minfo[39m: Chromedriver: [STDOUT] Starting ChromeDriver 2.18.343845 (73dd713ba7fbfb73cbb514e62641d8c96a94682a) on port 7115

APPLICATION URL:
[36minfo[39m: JSONWP Proxy: Proxying [POST /wd/hub/session/SESSION-ID/url] to [POST http://127.0.0.1:7115/wd/hub/session/SESSION-ID/url] with body: {"url":"http://www.w3schools.com/"}
[36minfo[39m: JSONWP Proxy: Got response with status 200: {"sessionId":"SESSION-ID","status":0,"value":null}

WEB ELEMENT:
[36minfo[39m: JSONWP Proxy: Proxying [POST /wd/hub/session/SESSION-ID/element] to [POST http://127.0.0.1:7115/wd/hub/session/SESSION-ID/element] with body: {"using":"xpath","value":"//body/div[4]/div[1]/div[1]/a[1]"}
[36minfo[39m: JSONWP Proxy: Got response with status 200: {"sessionId":"SESSION-ID","status":0,"value":{"ELEMENT":"0.39628730167672455-1"}}

WEB ELEMENT & ACTION:
[36minfo[39m: JSONWP Proxy: Proxying [POST /wd/hub/session/SESSION-ID/element/0.39628730167672455-1/click] to [POST http://127.0.0.1:7115/wd/hub/session/SESSION-ID/element/0.39628730167672455-1/click] with body: {"id":"0.39628730167672455-1"}
[36minfo[39m: JSONWP Proxy: Got response with status 200: {"sessionId":"SESSION-ID","status":0,"value":null}