Skip to content

Commit

Permalink
Add support to run iOS tests in non-mac machines (#1256)
Browse files Browse the repository at this point in the history
* initial support with go-ios

* pick go_ios from path

* catch tunnel start

* ignore tunnel start error

* move tunnel start to top level

* fix tunnel start

* add console.log to test in pi

* add go-ios for tracing

* update submodule

* add ios test script

* liveStreaming flag to true

* add checks for go-ios

* update submodule

* update submodule for ios linux support

---------

Co-authored-by: sudharsan selvaraj <[email protected]>
  • Loading branch information
saikrishna321 and sudharsan selvaraj authored Aug 2, 2024
1 parent 235ae90 commit 26b5732
Show file tree
Hide file tree
Showing 16 changed files with 138 additions and 22 deletions.
2 changes: 1 addition & 1 deletion dashboard-frontend
7 changes: 4 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
"test-remote": "mocha --require ts-node/register ./test/e2e/remote.spec.js --timeout 60000 --exit",
"test": "mocha -r ts-node/register ./test/unit/*.spec.{j,t}s --plugin-device-farm-platform=both --exit --timeout=20000",
"test-jest": "NODE_OPTIONS=--experimental-vm-modules npx jest ./test/unit/AndroidDeviceManager.spec.js",
"test-parallel-android": "mocha --require ts-node/register -p ./test/e2e/android/conf.spec.js --exit --timeout 260000",
"test-parallel-android": "mocha --require ts-node/register -p ./test/e2e/android/conf1.spec.js --exit --timeout 260000",
"test-parallel-ios": "mocha --require ts-node/register -p ./test/e2e/ios/conf1.spec.js --exit --timeout 260000",
"test-parallel-bs": "mocha --require ts-node/register -p ./test/e2e/android/cloud/bs.spec.ts --exit --timeout 260000",
"test-parallel-pcloudy": "mocha --require ts-node/register -p ./test/e2e/android/cloud/pcloudy.spec.ts --exit --timeout 260000",
"test-parallel-sauce": "wait-on http://localhost:31337/device-farm/ && mocha --require ts-node/register -p ./test/e2e/android/cloud/sauce.spec.js --exit --timeout 260000",
Expand All @@ -31,8 +32,8 @@
"buildAndCopyWeb": "sh buildAndCopyWeb.sh",
"prepublishOnly": "npx tsc && npm run buildAndCopyWeb && npm run copy-files && npm run bundle",
"lint": "eslint . --ext .ts,.tsx --fix",
"prettier-check": "prettier 'src/**/*.ts' 'web/**/*.ts' 'web/**/*.tsx' --check --verbose",
"prettier": "prettier 'src/**/*.ts' 'web/**/*.ts' 'web/**/*.tsx' 'test/**/*.ts' --write --single-quote",
"prettier-check": "prettier 'src/**/*.ts' --check --verbose",
"prettier": "prettier 'src/**/*.ts' 'test/**/*.ts' --write --single-quote",
"appium-home": "rm -rf /tmp/some-temp-dir && export APPIUM_HOME=/tmp/some-temp-dir",
"install-plugin": "npm run buildAndCopyWeb && npm run bundle && appium plugin install --source=local $(pwd)",
"clear-cache": "rm -rf $HOME/.cache/appium-device-farm",
Expand Down
4 changes: 2 additions & 2 deletions server-config.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
"device-farm": {
"platform": "both",
"skipChromeDownload": true,
"liveStreaming": true,
"wdaBundleId": "con.device.farm"
"iosDeviceType": "real",
"liveStreaming": true
}
}
}
Expand Down
13 changes: 7 additions & 6 deletions src/CapabilityManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,9 +61,7 @@ export async function androidCapabilities(
if (freeDevice.chromeDriverPath)
caps.firstMatch[0]['appium:chromedriverExecutable'] = freeDevice.chromeDriverPath;
if (!isCapabilityAlreadyPresent(caps, 'appium:mjpegServerPort')) {
caps.firstMatch[0]['appium:mjpegServerPort'] = !!options.liveVideo
? await getPort()
: undefined;
caps.firstMatch[0]['appium:mjpegServerPort'] = options.liveVideo ? await getPort() : undefined;
}
if (!options.liveVideo) {
deleteAlwaysMatch(caps, 'appium:mjpegServerPort');
Expand All @@ -79,6 +77,7 @@ export async function androidCapabilities(
export async function iOSCapabilities(
caps: ISessionCapability,
freeDevice: {
webDriverAgentUrl?: any;
udid: any;
name: string;
realDevice: boolean;
Expand All @@ -94,18 +93,20 @@ export async function iOSCapabilities(
caps.firstMatch[0]['appium:udid'] = freeDevice.udid;
caps.firstMatch[0]['appium:deviceName'] = freeDevice.name;
caps.firstMatch[0]['appium:platformVersion'] = freeDevice.sdk;
caps.firstMatch[0]['appium:wdaLocalPort'] = freeDevice.wdaLocalPort;
caps.firstMatch[0]['appium:mjpegServerPort'] = !!options.liveVideo
caps.firstMatch[0]['appium:mjpegServerPort'] = options.liveVideo
? freeDevice.mjpegServerPort
: undefined;
if (freeDevice.realDevice && !caps.firstMatch[0]['df:skipReport']) {
const wdaInfo = await prisma.appInformation.findFirst({
where: { fileName: 'wda-resign.ipa' },
});
if (wdaInfo) {
if (wdaInfo && !process.env.GO_IOS) {
caps.firstMatch[0]['appium:wdaLocalPort'] = freeDevice.wdaLocalPort;
caps.firstMatch[0]['appium:usePreinstalledWDA'] = true;
caps.firstMatch[0]['appium:updatedWDABundleId'] = wdaInfo.appBundleId;
caps.firstMatch[0]['appium:updatedWDABundleIdSuffix'] = '';
} else if (wdaInfo && process.env.GO_IOS) {
caps.firstMatch[0]['appium:webDriverAgentUrl'] = freeDevice.webDriverAgentUrl;
}
}

Expand Down
17 changes: 17 additions & 0 deletions src/device-managers/IOSDeviceManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ import { addNewDevice, removeDevice } from '../data-service/device-service';
import { DeviceTypeToInclude, IDerivedDataPath, IPluginArgs } from '../interfaces/IPluginArgs';
import { getDeviceInfo } from 'appium-ios-device/build/lib/utilities';
import { IOSDeviceInfoMap } from './IOSDeviceType';
import { exec } from 'child_process';
import semver from 'semver';

export default class IOSDeviceManager implements IDeviceManager {
constructor(
Expand Down Expand Up @@ -156,6 +158,20 @@ export default class IOSDeviceManager implements IDeviceManager {
});
} else {
const deviceInfo = await this.getDeviceInfo(udid, pluginArgs, hostPort);
const goIOS = process.env.GO_IOS;
if (goIOS && semver.satisfies(deviceInfo.sdk, '>=17.0.0')) {
//Check for version above 17+ and presence for Go IOS
try {
log.info('Running go-ios agent');
const startTunnel = `${goIOS} tunnel start --userspace --udid=${udid}`;
exec(startTunnel, (error, stdout, stderr) => {
console.log(`stdout: ${stdout}`);
console.error(`stderr: ${stderr}`);
});
} catch (err) {
log.error(err);
}
}
deviceState.push(deviceInfo);
}
});
Expand Down Expand Up @@ -231,6 +247,7 @@ export default class IOSDeviceManager implements IDeviceManager {
width: modelInfo.Width,
height: modelInfo.Height,
tags: [],
webDriverAgentUrl: `http://${pluginArgs.bindHostOrIp}:${wdaLocalPort}`,
});
}

Expand Down
11 changes: 8 additions & 3 deletions src/device-managers/iOSTracker.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
import usbmux from '../usbmux';
import GoIosTracker from '../goIOSTracker';
export class IosTracker {
private static instance: any;

public static getInstance(): any {
if (!IosTracker.instance) {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
IosTracker.instance = new usbmux.createListener();
if (process.env.GO_IOS) {
IosTracker.instance = new GoIosTracker();
} else {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
IosTracker.instance = new usbmux.createListener();
}
}

return IosTracker.instance;
Expand Down
1 change: 0 additions & 1 deletion src/device-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import {
isDeviceFarmRunning,
isMac,
} from './helpers';
import { ServerCLI } from './types/CLIArgs';
import { Platform } from './types/Platform';
import {
androidCapabilities,
Expand Down
88 changes: 88 additions & 0 deletions src/goIOSTracker.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import _ from 'lodash';
import { EventEmitter } from 'stream';
import { SubProcess } from 'teen_process';
import { cachePath } from './helpers';
import log from './logger';
export default class GoIosTracker extends EventEmitter {
private static instance: GoIosTracker;
private deviceMap: Map<number, string> = new Map();
private process!: SubProcess;
private started = true;

constructor() {
super();
this.start();
}

public static getInstance(): GoIosTracker {
if (!GoIosTracker.instance) {
GoIosTracker.instance = new GoIosTracker();
}

return GoIosTracker.instance;
}

start() {
if (!_.isNil(this.process) && this.process.isRunning) {
return;
}
let goIOSPath;
if (process.env.GO_IOS) {
log.info('Found GO_IOS in env');
goIOSPath = process.env.GO_IOS;
} else {
goIOSPath = `${cachePath('goIOS')}/ios`;
}
try {
this.process = new SubProcess(goIOSPath, ['listen']);
} catch (err: any) {
log.info(
`Failed to load go-ios ${goIOSPath}, iOS real device tracking not possible, please refer to link https://appium-device-farm-eight.vercel.app/troubleshooting/#ios-tracking for more details`,
);
}

this.process.on('lines-stdout', (out) => {
const parsedOutput = this.parseOutput(out);
if (!_.isNil(parsedOutput)) {
this.notify(parsedOutput);
}
});

this.process.on('exit', () => {
this.started = false;
this.emit('stop');
});

this.process.start(0);
}

async stop() {
if (_.isNil(this.process) || !this.process.isRunning) {
return;
}
this.process.stop('SIGINT');
this.started = false;
}

private parseOutput(output: any) {
try {
if (_.isArray(output)) {
return output.map((o) => JSON.parse(o));
}
} catch (err) {
return null;
}
}

private notify(messages: any[]) {
messages.forEach((message) => {
if (message.MessageType == 'Attached') {
this.deviceMap.set(message.DeviceID, message.Properties.SerialNumber);
this.emit('attached', message.Properties.SerialNumber);
} else {
const id = this.deviceMap.get(message.DeviceID);
this.emit('detached', id);
}
});
}
}
1 change: 1 addition & 0 deletions src/interfaces/IDevice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,5 @@ export interface IDevice {
proxySessionId?: string;
prebuiltWDAPath?: string;
tags: Array<string>;
webDriverAgentUrl?: string;
}
1 change: 1 addition & 0 deletions src/interfaces/IDeviceFarmSession.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export type IDeviceFarmSessionOptions = {
export type IBeforeSessionCreateEventOptions = {
device: IDevice;
sessionType: SessionType;
caps: any;
};

export type IAfterSessionDeletedEventOptions = {
Expand Down
2 changes: 1 addition & 1 deletion src/interfaces/IPluginArgs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ export const DefaultPluginArgs: IPluginArgs = {
enableDashboard: false,
removeDevicesFromDatabaseBeforeRunningThePlugin: false,
remoteConnectionTimeout: 60000,
liveStreaming: false,
liveStreaming: true,
wdaBundleId: '',
preBuildWDAPath: '',
};
2 changes: 1 addition & 1 deletion src/modules
6 changes: 4 additions & 2 deletions src/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ import { BeforeSessionCreatedEvent } from './events/before-session-create-event'
import SessionType from './enums/SessionType';
import { UnexpectedServerShutdownEvent } from './events/unexpected-server-shutdown-event';
import { AfterSessionDeletedEvent } from './events/after-session-deleted-event';
import { waitForCondition } from 'asyncbox';

const commandsQueueGuard = new AsyncLock();
const DEVICE_MANAGER_LOCK_NAME = 'DeviceManager';
Expand Down Expand Up @@ -338,8 +339,9 @@ class DevicePlugin extends BasePlugin {
: device.nodeId !== DevicePlugin.NODE_ID
? SessionType.REMOTE
: SessionType.LOCAL;

await EventBus.fire(new BeforeSessionCreatedEvent({ device, sessionType: sessionType }));
await EventBus.fire(
new BeforeSessionCreatedEvent({ device, sessionType: sessionType, caps }),
);
if (device.platform === 'ios' && device.realDevice) {
log.info(`📱 Forwarding ios port to real device ${device.udid} for manual interaction`);
await DEVICE_CONNECTIONS_FACTORY.requestConnection(device.udid, device.mjpegServerPort, {
Expand Down
1 change: 1 addition & 0 deletions src/usbmux.ts
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,7 @@ function createListener(): net.Socket {
}

// subsequent responses report on connected device status:
console.log(msg);
if (msg.MessageType === 'Attached') {
devices[msg.Properties.SerialNumber] = msg.Properties;
conn.emit('attached', msg.Properties.SerialNumber);
Expand Down
2 changes: 1 addition & 1 deletion test/e2e/ios/conf1.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ const capabilities = {
platformName: 'iOS',
'appium:automationName': 'XCUITest',
'appium:iPhoneOnly': true,
'appium:app': '/Users/saikrishna/Downloads/git/AppiumTestDistribution/apps/VodQAReactNative.zip',
'appium:bundleId': 'com.device.farm.wdioapp',
};
describe('Plugin1 Test', () => {
let driver;
Expand Down
2 changes: 1 addition & 1 deletion test/e2e/plugin-harness.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ export function pluginE2EHarness(opts: E2ESetupOpts & { enableGoIos?: boolean })
}

// find go_ios from npm
if (!!enableGoIos) env.GO_IOS = await goIosPath();
if (enableGoIos) env.GO_IOS = await goIosPath();

return env;
};
Expand Down

0 comments on commit 26b5732

Please sign in to comment.