Skip to content

Commit

Permalink
Adb remote (#620)
Browse files Browse the repository at this point in the history
* initial support for adbremote

* expose adbRemoteHost

* fix mock

* remove remoteAdb from config

* add adbRemoteHost to capabilties

* bump version and updated readMe
  • Loading branch information
saikrishna321 authored Jan 27, 2023
1 parent e797442 commit 2af28d8
Show file tree
Hide file tree
Showing 11 changed files with 217 additions and 76 deletions.
9 changes: 6 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,10 +102,13 @@ These arguments are set when you launch the Appium server, with this plugin inst
| `plugin-device-farm-device-types` | No | Types of devices to include | `both` | `both`,`simulated`,`real` |
| `plugin-device-farm-skip-chrome-download` | No | Downloads require chromedriver for web testing | `true` | `false` <br/>Setting to false will download required chromedriver for web testing on chrome |
| `plugin-device-farm-remote` | No | Whether or not to include simulators/real devices from remote machine | None | `remote: ["http://remotehost:remoteport"]`, If you want to run tests distributed across remote and local machine `remote: ["http://remotehost:remoteport", "http://127.0.0.1"]` |
| `plugin-device-farm-max-sessions` | No | Limit how many sessions can be active at a time. This is useful when you need limit sessions based on host machine resource availability. | None | `<number>` e.g. `8` |
| `plugin-device-farm-derived-data-path` | No | DriveDataPath of WDA to speed iOS test run. | None | `{'simulator': 'PathtoDrivedDataPath', 'device': 'PathtoDrivedDataPath'}` |
| `plugin-device-farm-max-sessions` | No | Limit how many sessions can be active at a time. This is useful when you need limit sessions based on host machine resource availability. | None | `<number>` e.g. `8` |
| `plugin-device-farm-derived-data-path` | No | DriveDataPath of WDA to speed iOS test run. | None | `{'simulator': 'PathtoDrivedDataPath', 'device': 'PathtoDrivedDataPath'}` |
| `plugin-device-farm-adb-remote` | No | ADB Remote host and port as array | None | `["remoteMachine1IP:adbPort", "remoteMachine2IP:adbPort"]` |

## Capabilities
Above cli arguments can also be set from config.json file Refer [here](https://github.com/AppiumTestDistribution/appium-device-farm/blob/main/sample-config.json)

## Capabilities

| Capability Name | Description |
| -------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
Expand Down
6 changes: 3 additions & 3 deletions install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,6 @@ echo 'Plugin List'

echo 'Installing UIAutomator2 driver'
./node_modules/.bin/appium driver install uiautomator2

echo 'Installing XCUIDriver driver'
./node_modules/.bin/appium driver install xcuitest
#
#echo 'Installing XCUIDriver driver'
#./node_modules/.bin/appium driver install xcuitest
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 5 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
{
"name": "appium-device-farm",
"version": "3.16.0",
"version": "3.17.0",
"description": "An appium 2.0 plugin that manages and create driver session on available devices",
"main": "./lib/index.js",
"scripts": {
"test": "mocha -r ts-node/register ./test/unit/*.spec.js --plugin-device-farm-platform=both --exit --timeout=10000",
"test-jest": "NODE_OPTIONS=--experimental-vm-modules npx jest ./test/unit/AndroidDeviceManager.spec.js",
"test-parallel-android": "wait-on http://localhost:31337/device-farm/ && mocha --require ts-node/register -p ./test/e2e/android/*.spec.js --timeout 260000",
"test-parallel-android": "mocha --require ts-node/register -p ./test/e2e/android/conf.spec.js --timeout 260000",
"test-parallel-bs": "mocha --require ts-node/register -p ./test/e2e/android/cloud/conf.spec.js --timeout 260000",
"test-parallel-pcloudy": "mocha --require ts-node/register -p ./test/e2e/android/cloud/pcloudy.spec.js --timeout 260000",
"test-parallel-sauce": "mocha --require ts-node/register -p ./test/e2e/android/cloud/sauce.spec.js --timeout 260000",
Expand Down Expand Up @@ -137,6 +137,9 @@
"remote": {
"type": "array"
},
"adbRemote": {
"type": "array"
},
"skipChromeDownload": {
"type": "boolean"
},
Expand Down
18 changes: 18 additions & 0 deletions sample-config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"server": {
"port": 31337,
"plugin": {
"device-farm": {
"platform": "android",
"skipChromeDownload": true,
"adbRemote": ["192.168.0.1:5037", "192.168.0.2:5037"],
"remote": ["http://firstMachineIP:appiumPort", "http://secondMachineIP:appiumPort"],
"maxSessions": 2,
"derivedDataPath": {
"simulator": "/Users/saikrishna/Library/Developer/Xcode/DerivedData/WebDriverAgent-Test",
"device": "/Users/saikrishna/Library/Developer/Xcode/DerivedData/WebDriverAgent-Test"
}
}
}
}
}
10 changes: 6 additions & 4 deletions src/CapabilityManager.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import getPort from 'get-port';
import { ISessionCapability } from './interfaces/ISessionCapability';
import _ from 'lodash';
import { IDevice } from './interfaces/IDevice';

function isCapabilityAlreadyPresent(caps: ISessionCapability, capabilityName: string) {
return _.has(caps.alwaysMatch, capabilityName) || _.has(caps.firstMatch[0], capabilityName);
Expand All @@ -10,13 +11,12 @@ function deleteAlwaysMatch(caps: ISessionCapability, capabilityName: string) {
if (_.has(caps.alwaysMatch, capabilityName)) delete caps.alwaysMatch[capabilityName];
}

export async function androidCapabilities(
caps: ISessionCapability,
freeDevice: { udid: any; name: string; systemPort: number; chromeDriverPath?: any }
) {
export async function androidCapabilities(caps: ISessionCapability, freeDevice: IDevice) {
caps.firstMatch[0]['appium:udid'] = freeDevice.udid;
caps.firstMatch[0]['appium:systemPort'] = await getPort();
caps.firstMatch[0]['appium:chromeDriverPort'] = await getPort();
caps.firstMatch[0]['appium:adbRemoteHost'] = freeDevice.adbRemoteHost;
caps.firstMatch[0]['appium:adbPort'] = freeDevice.adbPort;
if (freeDevice.chromeDriverPath)
caps.firstMatch[0]['appium:chromedriverExecutable'] = freeDevice.chromeDriverPath;
if (!isCapabilityAlreadyPresent(caps, 'appium:mjpegServerPort')) {
Expand All @@ -25,6 +25,8 @@ export async function androidCapabilities(
deleteAlwaysMatch(caps, 'appium:udid');
deleteAlwaysMatch(caps, 'appium:systemPort');
deleteAlwaysMatch(caps, 'appium:chromeDriverPort');
deleteAlwaysMatch(caps, 'appium:adbRemoteHost');
deleteAlwaysMatch(caps, 'appium:adbPort');
}

export async function iOSCapabilities(
Expand Down
132 changes: 80 additions & 52 deletions src/device-managers/AndroidDeviceManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,47 +60,58 @@ export default class AndroidDeviceManager implements IDeviceManager {
cliArgs: any
) {
await this.requireSdkRoot();
const connectedDevices = await this.getConnectedDevices();
await asyncForEach(connectedDevices, async (device: IDevice) => {
if (!deviceState.find((devicestate) => devicestate.udid === device.udid)) {
const existingDevice = existingDeviceDetails.find(
(dev) => dev.udid === device.udid && dev.host.includes('127.0.0.1')
);
if (existingDevice) {
log.info(`Android Device details for ${device.udid} already available`);
deviceState.push({
...existingDevice,
busy: false,
});
} else {
log.info(`Android Device details for ${device.udid} not available. So querying now.`);
const systemPort = await getFreePort();
const totalUtilizationTimeMilliSec = await getUtilizationTime(device.udid);
const [sdk, realDevice, name, chromeDriverPath] = await Promise.all([
this.getDeviceVersion(device.udid),
this.isRealDevice(device.udid),
this.getDeviceName(device.udid),
this.getChromeVersion(device.udid, cliArgs),
]);

deviceState.push({
systemPort,
sdk,
realDevice,
name,
busy: false,
state: device.state,
udid: device.udid,
platform: 'android',
deviceType: realDevice ? 'real' : 'emulator',
host: `http://127.0.0.1:${cliArgs.port}`,
totalUtilizationTimeMilliSec: totalUtilizationTimeMilliSec,
sessionStartTime: 0,
chromeDriverPath,
});
const connectedDevices = await this.getConnectedDevices(cliArgs);
for (const [adbInstance, devices] of connectedDevices) {
await asyncForEach(devices, async (device: IDevice) => {
device.adbRemoteHost =
adbInstance.adbRemoteHost === null ? '127.0.0.1' : adbInstance.adbRemoteHost;
if (
!deviceState.find(
(devicestate) =>
devicestate.udid === device.udid && devicestate.adbRemoteHost === device.adbRemoteHost
)
) {
const existingDevice = existingDeviceDetails.find(
(dev) => dev.udid === device.udid && dev.host.includes('127.0.0.1')
);
if (existingDevice) {
log.info(`Android Device details for ${device.udid} already available`);
deviceState.push({
...existingDevice,
busy: false,
});
} else {
log.info(`Android Device details for ${device.udid} not available. So querying now.`);
const systemPort = await getFreePort();
const totalUtilizationTimeMilliSec = await getUtilizationTime(device.udid);
const [sdk, realDevice, name, chromeDriverPath] = await Promise.all([
this.getDeviceVersion(adbInstance, device.udid),
this.isRealDevice(adbInstance, device.udid),
this.getDeviceName(adbInstance, device.udid),
this.getChromeVersion(adbInstance, device.udid, cliArgs),
]);
const host = adbInstance.adbHost != null ? adbInstance.adbHost : '127.0.0.1';
deviceState.push({
adbRemoteHost: adbInstance.adbHost,
adbPort: adbInstance.adbPort,
systemPort,
sdk,
realDevice,
name,
busy: false,
state: device.state,
udid: device.udid,
platform: 'android',
deviceType: realDevice ? 'real' : 'emulator',
host: `http://${host}:${cliArgs.port}`,
totalUtilizationTimeMilliSec: totalUtilizationTimeMilliSec,
sessionStartTime: 0,
chromeDriverPath,
});
}
}
}
});
});
}
return deviceState;
}

Expand All @@ -115,11 +126,28 @@ export default class AndroidDeviceManager implements IDeviceManager {
return this.adb;
}

public async getConnectedDevices() {
return await (await this.getAdb()).getConnectedDevices();
public async getConnectedDevices(cliArgs: any) {
const deviceList = new Map();
const originalADB = await this.getAdb();
deviceList.set(originalADB, await originalADB.getConnectedDevices());
const adbRemote = cliArgs.plugin['device-farm'].adbRemote;
if (adbRemote !== undefined && adbRemote.length > 0) {
await asyncForEach(adbRemote, async (value: any) => {
const adbRemoteValue = value.split(':');
console.log(adbRemoteValue, value);
const adbHost = adbRemoteValue[0];
const adbPort = adbRemoteValue[1] || 5037;
const cloneAdb = originalADB.clone({
remoteAdbHost: adbHost,
adbPort,
});
deviceList.set(cloneAdb, await cloneAdb.getConnectedDevices());
});
}
return deviceList;
}

public async getChromeVersion(udid: string, cliArgs: any) {
public async getChromeVersion(adbInstance: any, udid: string, cliArgs: any) {
if (cliArgs.plugin['device-farm'].skipChromeDownload) {
log.warn('skipChromeDownload server arg is set; skipping Chromedriver installation.');
log.warn('Android web/hybrid testing will not be possible without Chromedriver.');
Expand All @@ -130,7 +158,7 @@ export default class AndroidDeviceManager implements IDeviceManager {
let versionName = '';
try {
const stdout = await (
await this.getAdb()
await adbInstance
).adbExec(['-s', udid, 'shell', 'dumpsys', 'package', 'com.android.chrome']);
const versionNameMatch = new RegExp(/versionName=([\d+.]+)/).exec(stdout);
if (versionNameMatch) {
Expand All @@ -148,16 +176,16 @@ export default class AndroidDeviceManager implements IDeviceManager {
return await instance.downloadChromeDriver(version);
}

private async getDeviceVersion(udid: string) {
return await this.getDeviceProperty(udid, 'ro.build.version.release');
private async getDeviceVersion(adbInstance: any, udid: string) {
return await this.getDeviceProperty(adbInstance, udid, 'ro.build.version.release');
}

private async getDeviceProperty(udid: string, prop: string) {
return await (await this.getAdb()).adbExec(['-s', udid, 'shell', 'getprop', prop]);
private async getDeviceProperty(adbInstance: any, udid: string, prop: string) {
return await (await adbInstance).adbExec(['-s', udid, 'shell', 'getprop', prop]);
}

private async isRealDevice(udid: string): Promise<boolean> {
const character = await this.getDeviceProperty(udid, 'ro.build.characteristics');
private async isRealDevice(adbInstance: any, udid: string): Promise<boolean> {
const character = await this.getDeviceProperty(adbInstance, udid, 'ro.build.characteristics');
return character !== 'emulator';
}

Expand All @@ -183,6 +211,6 @@ export default class AndroidDeviceManager implements IDeviceManager {
return sdkRoot;
}

private getDeviceName = async (udid: string) =>
await this.getDeviceProperty(udid, 'ro.product.name');
private getDeviceName = async (adbInstance: any, udid: string) =>
await this.getDeviceProperty(adbInstance, udid, 'ro.product.name');
}
2 changes: 2 additions & 0 deletions src/interfaces/IDevice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,6 @@ export interface IDevice {
derivedDataPath?: string;
chromeDriverPath?: any;
capability?: any;
adbRemoteHost: string;
adbPort: number;
}
Loading

0 comments on commit 2af28d8

Please sign in to comment.