Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 27 additions & 45 deletions src/device-managers/IOSDeviceManager.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,23 @@
import Simctl from 'node-simctl';
// eslint-disable-next-line @typescript-eslint/no-var-requires
const { flatten, isEmpty } = require('lodash');
import { utilities as IOSUtils } from 'appium-ios-device';
import { IDevice } from '../interfaces/IDevice';
import { IDeviceManager } from '../interfaces/IDeviceManager';
import { asyncForEach, getFreePort } from '../helpers';
import log from '../logger';
import { getDeviceInfo } from 'appium-ios-device/build/lib/utilities';
import Simctl from 'node-simctl';
import os from 'os';
import path from 'path';
// eslint-disable-next-line @typescript-eslint/no-var-requires
const fs = require('fs-extra');
import Devices from './cloud/Devices';
import NodeDevices from './NodeDevices';
import { IosTracker } from './iOSTracker';
import { addNewDevice, generateDeviceId, removeDevice } from '../data-service/device-service';
import { startTunnel } from '../goIOSTracker';
import { asyncForEach, getFreePort } from '../helpers';
import { IDevice } from '../interfaces/IDevice';
import { IDeviceManager } from '../interfaces/IDeviceManager';
import { DeviceTypeToInclude, IDerivedDataPath, IPluginArgs } from '../interfaces/IPluginArgs';
import { getDeviceInfo } from 'appium-ios-device/build/lib/utilities';
import log from '../logger';
import Devices from './cloud/Devices';
import { IOSDeviceInfoMap } from './IOSDeviceType';
import { exec } from 'child_process';
import semver from 'semver';

import { IosTracker } from './iOSTracker';
import NodeDevices from './NodeDevices';
// eslint-disable-next-line @typescript-eslint/no-var-requires
const { flatten, isEmpty } = require('lodash');
// eslint-disable-next-line @typescript-eslint/no-var-requires
const fs = require('fs-extra');
interface IDeviceInfo {
ProductType?: string;
ProductName?: string;
Expand Down Expand Up @@ -245,6 +243,7 @@ export default class IOSDeviceManager implements IDeviceManager {
): Promise<IDevice[]> {
const devices = await this.getConnectedDevices();
const deviceState: IDevice[] = [];
this.trackIOSDevices(pluginArgs);
await asyncForEach(devices, async (udid: string) => {
const existingDevice = existingDeviceDetails.find((device) => device.udid === udid);
if (existingDevice) {
Expand All @@ -257,44 +256,20 @@ export default class IOSDeviceManager implements IDeviceManager {
} else {
log.debug(`Getting device info for ${udid}`);
const deviceInfo = await this.getDeviceInfo(udid, pluginArgs, hostPort);
const goIOS = process.env.GO_IOS;
log.info(`Go IOS: ${goIOS}`);
const sdkRaw = deviceInfo.sdk?.toString();
const sdkNormalized = sdkRaw ? sdkRaw.trim().toLowerCase().replace(/x/g, '0') : undefined;
const sdkCoerced = semver.coerce(sdkNormalized ?? sdkRaw)?.version;
const isAtLeast17 = sdkCoerced ? semver.satisfies(sdkCoerced, '>=17.0.0') : false;
log.info(`Device SDK: ${sdkRaw}`);
if (sdkNormalized && sdkNormalized !== sdkRaw) {
log.info(`Normalized SDK: ${sdkNormalized}`);
}
log.info(`Coerced SDK: ${sdkCoerced ?? 'invalid'}`);
log.info(`Semver satisfies (>=17.0.0): ${isAtLeast17}`);
if (goIOS && isAtLeast17) {
//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);
}
});
// might as well track devices
await this.trackIOSDevices(pluginArgs);

return deviceState;
}

async trackIOSDevices(pluginArgs: IPluginArgs) {
trackIOSDevices(pluginArgs: IPluginArgs) {
const iosTracker = IosTracker.getInstance();
iosTracker.on('attached', async (udid: string) => {
log.info(`Attached iOS device ${udid}`);
const deviceAttached = await this.getDeviceInfo(udid, pluginArgs, this.hostPort);
if (deviceAttached.goIOSAgentPort != undefined) {
await startTunnel(udid, deviceAttached.sdk, deviceAttached.goIOSAgentPort);
}
const deviceTracked: IDevice = {
...deviceAttached,
nodeId: this.nodeId,
Expand Down Expand Up @@ -327,6 +302,7 @@ export default class IOSDeviceManager implements IDeviceManager {
pluginArgs: IPluginArgs,
hostPort: number,
): Promise<IDevice> {
log.info(`Getting device info for ${udid}`);
let host;
if (pluginArgs.remoteMachineProxyIP) {
host = pluginArgs.remoteMachineProxyIP;
Expand All @@ -347,6 +323,11 @@ export default class IOSDeviceManager implements IDeviceManager {
modelInfo.Height,
deviceInfo,
);

// Generate goIOSAgentPort if GO_IOS environment variable is set
const goIOS = process.env.GO_IOS;
const goIOSAgentPort = goIOS ? await getFreePort(pluginArgs.portRange) : undefined;

return Object.assign({
id: generateDeviceId({
udid: udid,
Expand All @@ -373,6 +354,7 @@ export default class IOSDeviceManager implements IDeviceManager {
tags: [],
webDriverAgentHost: `http://${pluginArgs.bindHostOrIp}`,
webDriverAgentUrl: `http://${pluginArgs.bindHostOrIp}:${wdaLocalPort}`,
...(goIOSAgentPort && { goIOSAgentPort }),
});
}

Expand Down
72 changes: 72 additions & 0 deletions src/goIOSTracker.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { exec } from 'child_process';
import _ from 'lodash';
import semver from 'semver';
import { EventEmitter } from 'stream';
import { SubProcess } from 'teen_process';
import { cachePath } from './helpers';
Expand Down Expand Up @@ -86,3 +88,73 @@ export default class GoIosTracker extends EventEmitter {
});
}
}

/**
* Start a go-ios tunnel for a device
* @param udid - The device UDID
* @param sdk - The device SDK version
* @param goIOSAgentPort - The port to use for the go-ios agent (optional)
*/
export async function startTunnel(
udid: string,
sdk?: string,
goIOSAgentPort?: number,
): Promise<void> {
const goIOS = process.env.GO_IOS;
log.info(`Go IOS: ${goIOS}`);

// Check if GO_IOS is configured
if (!goIOS) {
log.info('GO_IOS environment variable not set, skipping tunnel setup');
return;
}

// Check if goIOSAgentPort is provided
if (!goIOSAgentPort) {
log.info('Go IOS Agent Port not provided, skipping tunnel setup');
return;
}

// SDK version checking
const sdkRaw = sdk?.toString();
const sdkNormalized = sdkRaw ? sdkRaw.trim().toLowerCase().replace(/x/g, '0') : undefined;
const sdkCoerced = semver.coerce(sdkNormalized ?? sdkRaw)?.version;
const isAtLeast17 = sdkCoerced ? semver.satisfies(sdkCoerced, '>=17.0.0') : false;

log.info(`Device SDK: ${sdkRaw}`);
if (sdkNormalized && sdkNormalized !== sdkRaw) {
log.info(`Normalized SDK: ${sdkNormalized}`);
}
log.info(`Coerced SDK: ${sdkCoerced ?? 'invalid'}`);
log.info(`Semver satisfies (>=17.0.0): ${isAtLeast17}`);
log.info(`Go IOS Agent Port: ${goIOSAgentPort} for device ${udid}`);

// Check for version above 17+ and presence for Go IOS
if (!isAtLeast17) {
log.info(`Device SDK version ${sdkRaw} is below 17.0.0, skipping go-ios tunnel setup`);
return;
}

try {
log.info('Running go-ios agent');
const startTunnelCmd = `GO_IOS_AGENT_PORT=${goIOSAgentPort} ${goIOS} tunnel start --userspace --udid=${udid}`;
log.info(`Starting go-ios tunnel: ${startTunnelCmd}`);

exec(startTunnelCmd, (error, stdout, stderr) => {
if (error) {
log.error(`Error starting go-ios tunnel: ${error.message}`);
return;
}
if (stdout) {
log.info(`go-ios tunnel stdout: ${stdout}`);
}
if (stderr) {
log.warn(`go-ios tunnel stderr: ${stderr}`);
}
log.info('go-ios tunnel established successfully');
});
} catch (err) {
log.error(`Failed to establish go-ios tunnel: ${err}`);
throw err;
}
}
1 change: 1 addition & 0 deletions src/interfaces/IDevice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ export interface IDevice {
tags: Array<string>;
webDriverAgentUrl?: string;
webDriverAgentHost?: string;
goIOSAgentPort?: number;
sessionResponse?: Record<string, any>;
activeUser?: {
id: string;
Expand Down
2 changes: 1 addition & 1 deletion src/modules