diff --git a/src/device-managers/IOSDeviceManager.ts b/src/device-managers/IOSDeviceManager.ts index 8b14f61c2..0843851cd 100644 --- a/src/device-managers/IOSDeviceManager.ts +++ b/src/device-managers/IOSDeviceManager.ts @@ -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; @@ -245,6 +243,7 @@ export default class IOSDeviceManager implements IDeviceManager { ): Promise { 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) { @@ -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, @@ -327,6 +302,7 @@ export default class IOSDeviceManager implements IDeviceManager { pluginArgs: IPluginArgs, hostPort: number, ): Promise { + log.info(`Getting device info for ${udid}`); let host; if (pluginArgs.remoteMachineProxyIP) { host = pluginArgs.remoteMachineProxyIP; @@ -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, @@ -373,6 +354,7 @@ export default class IOSDeviceManager implements IDeviceManager { tags: [], webDriverAgentHost: `http://${pluginArgs.bindHostOrIp}`, webDriverAgentUrl: `http://${pluginArgs.bindHostOrIp}:${wdaLocalPort}`, + ...(goIOSAgentPort && { goIOSAgentPort }), }); } diff --git a/src/goIOSTracker.ts b/src/goIOSTracker.ts index 51da01668..885da3219 100644 --- a/src/goIOSTracker.ts +++ b/src/goIOSTracker.ts @@ -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'; @@ -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 { + 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; + } +} diff --git a/src/interfaces/IDevice.ts b/src/interfaces/IDevice.ts index d36eb9e4e..ff04bb104 100644 --- a/src/interfaces/IDevice.ts +++ b/src/interfaces/IDevice.ts @@ -42,6 +42,7 @@ export interface IDevice { tags: Array; webDriverAgentUrl?: string; webDriverAgentHost?: string; + goIOSAgentPort?: number; sessionResponse?: Record; activeUser?: { id: string; diff --git a/src/modules b/src/modules index 74e019bd8..04cee7c69 160000 --- a/src/modules +++ b/src/modules @@ -1 +1 @@ -Subproject commit 74e019bd8ef2bcbb7b11d021d740f0dd71a40585 +Subproject commit 04cee7c69cf6c99011fcfc5309c81844fece2c75