How to use Existing / Already Running iOS Simulator?

Is there a way to use an existing, already running iOS Simulator?

I’m able to do this with Android using the following capabilities:

{
  "platformName": "Android",
  "appium:automationName": "UIAutomator2",
  "appium:noReset": true
}

When I try something similar for iOS:

{
  "platformName": "iOS",
  "appium:automationName": "XCUITest",
  "appium:noReset": true
}

It launches a new Simulator using the latest platform version.

Logs seem to indicate the same:

Appium v2.2.3 creating new XCUITestDriver (v5.12.2) session
[AppiumDriver@9265] Checking BaseDriver versions for Appium and XCUITestDriver
[AppiumDriver@9265] Appium's BaseDriver version is 9.4.3
[AppiumDriver@9265] XCUITestDriver's BaseDriver version is 9.4.3
[XCUITestDriver@9ef2] Creating session with W3C capabilities: {
[XCUITestDriver@9ef2]   "alwaysMatch": {
[XCUITestDriver@9ef2]     "platformName": "iOS",
[XCUITestDriver@9ef2]     "appium:automationName": "XCUITest",
[XCUITestDriver@9ef2]     "appium:noReset": true,
[XCUITestDriver@9ef2]     "appium:includeSafariInWebviews": true,
[XCUITestDriver@9ef2]     "appium:newCommandTimeout": 3600,
[XCUITestDriver@9ef2]     "appium:connectHardwareKeyboard": true
[XCUITestDriver@9ef2]   },
[XCUITestDriver@9ef2]   "firstMatch": [
[XCUITestDriver@9ef2]     {}
[XCUITestDriver@9ef2]   ]
[XCUITestDriver@9ef2] }
[XCUITestDriver@9ef2] The desired capabilities include neither an app nor a bundleId. WebDriverAgent will be started without the default app
[XCUITestDriver@9ef2] 'platformVersion' capability ('undefined') is not a valid version number. Consider fixing it or be ready to experience an inconsistent driver behavior.
[XCUITestDriver@9ef2 (ae6fd124)] Session created with session id: ae6fd124-c608-41e7-a7d5-710175ad8755
[XCUITest] Current user: 'nick.derevjanik'
[XCUITestDriver@9ef2 (ae6fd124)] No real device udid has been provided in capabilities. Will select a matching simulator to run the test.
[XCUITestDriver@9ef2 (ae6fd124)] iOS SDK Version set to '17.2'
[XCUITestDriver@9ef2 (ae6fd124)] No platformVersion specified. Using the latest version Xcode supports: '17.2'. This may cause problems if a simulator does not exist for this platform version.
[XCUITest] Looking for an existing Simulator with platformName: iOS, platformVersion: 17.2, deviceName: undefined
[XCUITest] The 'deviceName' capability value is empty. Selecting the first matching device 'iPhone SE (3rd generation)' having the 'platformVersion' set to 17.2

This is pretty easy to duplicate:

  1. Ensure an iOS Simulator is running (that is not the latest platform version)
  2. npx appium driver install xcuitest
  3. npx appium --allow-cors
  4. Appium Web Inspector
  5. Use the iOS capabilities mentioned above
  6. Start session
  7. Observe: a new iOS simulator is created

Consider providing either platformVersion/deviceName or UUID of an existing iOS simulator.

@mykola-mokhnach I appreciate the feedback, but I’m looking for something automatic. I don’t want to hardcode or have to modify code if the simulator being used differs. I’m trying to create a nice developer experience for my team, that might be running different simulators.

I was thinking I might need to parse the output of xcrun simctl.

I was hoping for something native to Appium or WDIO.

Any other ideas?

xcuitest driver must know what device model to use (platform version is autodetected based on the current xcode version if not provided explicitly). It does not support random simulator device selection. Although, for real devices one may set appium:udid to auto in order to select the first connected device.

Maybe parsing simctl output would be an option for you.

1 Like

I ended up parsing simctl, as @mykola-mokhnach recommended:

import { execSync } from 'node:child_process'

type DeviceMap = {
  [sdk: string]: Device[]
}

type Device = {
  name: string
  udid: string
}

export function getBootedSimulators(): Device[] {
  const output = execSync('xcrun simctl list devices booted -je')
  const devicesMap: DeviceMap = JSON.parse(output.toString()).devices
  return Object.values(devicesMap).flatMap((devices) => devices)
}

Here’s a unit test as an example of usage:

import { afterEach, describe, expect, it, jest } from '@jest/globals'
import * as child_process from 'node:child_process'
import { getBootedSimulators } from './environment'

describe('environment', function () {
  afterEach(function () {
    jest.resetAllMocks()
  })

  describe('getBootedSimulators', function () {
    it('should return a flattened array', function () {
      const mockOutput = {
        devices: {
          'com.apple.CoreSimulator.SimRuntime.iOS-17-2': [
            {
              udid: 'B0B688C9-079E-4701-90BF-766E66962F9E',
              name: 'iPhone SE (3rd generation)',
            },
          ],
          'com.apple.CoreSimulator.SimRuntime.iOS-15-5': [
            {
              udid: 'F4C1C9D3-D31C-4926-9240-6F9EE1CD2A90',
              name: 'iPhone 13',
            },
          ],
        },
      }

      jest.spyOn(child_process, 'execSync').mockReturnValue(JSON.stringify(mockOutput))
      const devices = getBootedSimulators()

      expect(devices).toHaveLength(2)
      expect(devices[0].name).toEqual('iPhone SE (3rd generation)')
      expect(devices[0].udid).toEqual('B0B688C9-079E-4701-90BF-766E66962F9E')
      expect(devices[1].name).toEqual('iPhone 13')
      expect(devices[1].udid).toEqual('F4C1C9D3-D31C-4926-9240-6F9EE1CD2A90')
    })
  })
})