Skip to main content
The iOS Client provides comprehensive methods to control and interact with iOS Simulator instances.

createInstanceClient

Creates a client for interacting with a Limrun iOS instance.
import { createInstanceClient } from '@limrun/ios-client';

const client = await createInstanceClient({
  apiUrl: 'https://instance.limrun.com',
  token: 'your-token',
  logLevel: 'info'
});

Parameters

options
InstanceClientOptions
required
Configuration options for the iOS client

Returns

client
Promise<InstanceClient>
A promise that resolves to an InstanceClient instance with deviceInfo populated

InstanceClient

The client interface for interacting with an iOS Simulator instance.

Properties

deviceInfo

Device information fetched during client initialization.
console.log(client.deviceInfo);
// {
//   udid: 'ABC123-DEF456',
//   screenWidth: 390,
//   screenHeight: 844,
//   model: 'iPhone 14 Pro'
// }
deviceInfo
DeviceInfo

Screen Interaction Methods

screenshot

Take a screenshot of the current screen.
const screenshot = await client.screenshot();
console.log(`Screenshot: ${screenshot.width}x${screenshot.height}`);
const imageData = Buffer.from(screenshot.base64, 'base64');
screenshot
Promise<ScreenshotData>

tap

Tap at the specified coordinates using the device’s native screen dimensions.
await client.tap(195, 422);
x
number
required
X coordinate in points
y
number
required
Y coordinate in points

tapWithScreenSize

Tap at coordinates with explicit screen size. Use this when coordinates are in a different coordinate space than the device’s native dimensions.
// Tap at 50% of a 1080x1920 screenshot
await client.tapWithScreenSize(540, 960, 1080, 1920);
x
number
required
X coordinate in the provided screen coordinate space
y
number
required
Y coordinate in the provided screen coordinate space
screenWidth
number
required
Width of the coordinate space
screenHeight
number
required
Height of the coordinate space

scroll

Scroll in a direction by a specified number of pixels.
// Scroll down 300 pixels from center
await client.scroll('down', 300);

// Scroll with custom starting point and momentum
await client.scroll('up', 500, {
  coordinate: [195, 400],
  momentum: 0.8 // Fast scroll with inertia
});
direction
'up' | 'down' | 'left' | 'right'
required
Direction content moves
pixels
number
required
Total pixels to scroll (finger movement distance)
options
object

setOrientation

Set the device orientation.
await client.setOrientation('Landscape');
orientation
'Portrait' | 'Landscape'
required
The orientation to set

Accessibility Element Methods

elementTree

Get the element tree (accessibility hierarchy) of the current screen.
const tree = await client.elementTree();
console.log(JSON.parse(tree));

// Get element at specific point
const elementAtPoint = await client.elementTree({ x: 100, y: 200 });
point
AccessibilityPoint
Optional point to get the element at that specific location
tree
Promise<string>
JSON string of the accessibility tree

tapElement

Tap an accessibility element by selector.
// Tap by accessibility identifier
await client.tapElement({ accessibilityId: 'login-button' });

// Tap by label
await client.tapElement({ label: 'Sign In' });

// Tap by element type and label contains
await client.tapElement({
  elementType: 'Button',
  labelContains: 'continue'
});
selector
AccessibilitySelector
required
The selector criteria to find the element. All non-undefined fields must match.
result
Promise<TapElementResult>

incrementElement

Increment an accessibility element (useful for sliders, steppers, etc.).
await client.incrementElement({ accessibilityId: 'volume-slider' });
selector
AccessibilitySelector
required
The selector criteria to find the element
result
Promise<ElementResult>

decrementElement

Decrement an accessibility element (useful for sliders, steppers, etc.).
await client.decrementElement({ accessibilityId: 'volume-slider' });
selector
AccessibilitySelector
required
The selector criteria to find the element
result
Promise<ElementResult>

setElementValue

Set the value of an accessibility element. This is much faster than typing character by character.
await client.setElementValue('[email protected]', {
  accessibilityId: 'email-field'
});
text
string
required
The text value to set
selector
AccessibilitySelector
required
The selector criteria to find the element
result
Promise<ElementResult>

Keyboard Input Methods

typeText

Type text into the currently focused input field.
await client.typeText('Hello, World!');

// Type and press Enter
await client.typeText('search query', true);
text
string
required
The text to type
pressEnter
boolean
default:false
If true, press Enter after typing

pressKey

Press a key on the keyboard, optionally with modifiers.
// Press Enter
await client.pressKey('enter');

// Press Command+S
await client.pressKey('s', ['command']);

// Press Command+Shift+F
await client.pressKey('f', ['command', 'shift']);
key
string
required
The key to press (e.g., ‘a’, ‘enter’, ‘backspace’, ‘up’, ‘f1’)
modifiers
string[]
Optional modifier keys (e.g., [‘shift’], [‘command’, ‘shift’])

toggleKeyboard

Toggle the on-screen software keyboard visibility. Equivalent to pressing Cmd+K in the iOS Simulator.
await client.toggleKeyboard();

App Management Methods

launchApp

Launch an installed app by bundle identifier.
// Launch and bring to foreground if already running
await client.launchApp('com.example.app');

// Terminate and relaunch if already running
await client.launchApp('com.example.app', 'RelaunchIfRunning');
bundleId
string
required
Bundle identifier of the app to launch
mode
'ForegroundIfRunning' | 'RelaunchIfRunning'
default:"ForegroundIfRunning"
Launch mode:
  • 'ForegroundIfRunning': bring to foreground if already running
  • 'RelaunchIfRunning': terminate and relaunch if already running

terminateApp

Terminate a running app by bundle identifier. Succeeds silently if the app is not currently running.
await client.terminateApp('com.example.app');
bundleId
string
required
Bundle identifier of the app to terminate

installApp

Install an app from a URL (supports .ipa or .app files, optionally zipped).
// Basic installation
const result = await client.installApp('https://example.com/app.ipa');
console.log(`Installed: ${result.bundleId}`);

// Install with caching and auto-launch
const result2 = await client.installApp('https://example.com/app.ipa', {
  md5: 'abc123...', // Skip download if cached version matches
  launchMode: 'RelaunchIfRunning'
});
url
string
required
The URL to download the app from
options
AppInstallationOptions
result
Promise<AppInstallationResult>

listApps

List installed apps on the simulator.
const apps = await client.listApps();
for (const app of apps) {
  console.log(`${app.name} (${app.bundleId}) - ${app.installType}`);
}
apps
Promise<InstalledApp[]>

syncApp

Sync an iOS app bundle folder to the server and optionally install/launch it.
// Sync and install
const result = await client.syncApp('/path/to/MyApp.app', {
  install: true,
  launchMode: 'RelaunchIfRunning'
});

// Watch mode for development
const result2 = await client.syncApp('/path/to/MyApp.app', {
  install: true,
  watch: true,
  maxPatchBytes: 1024 * 1024 // 1MB patch limit
});
localAppBundlePath
string
required
Path to the local .app bundle
options
object
result
Promise<SyncFolderResult>
Sync operation result

openUrl

Open a URL in the simulator. Web URLs open in Safari, deep links open corresponding apps.
// Open web URL
await client.openUrl('https://example.com');

// Open deep link
await client.openUrl('myapp://profile/123');
url
string
required
The URL to open

Logging Methods

appLogTail

Fetch the last N lines of app logs (combined stdout/stderr).
const logs = await client.appLogTail('com.example.app', 100);
console.log(logs);
bundleId
string
required
Bundle identifier of the app
lines
number
required
Number of lines to return (clamped to server limit)
logs
Promise<string>
The log output

streamAppLog

Stream app logs for a bundle ID (batched lines every ~500ms).
const stream = client.streamAppLog('com.example.app');

stream.on('line', (line) => {
  console.log('[APP]', line);
});

stream.on('error', (error) => {
  console.error('Stream error:', error);
});

// Stop streaming when done
stream.stop();
bundleId
string
required
Bundle identifier of the app
stream
LogStream
LogStream handle with the following events:
  • line: (line: string) => void - Single log line
  • lines: (lines: string[]) => void - Batch of log lines
  • error: (error: Error) => void - Stream error
  • close: () => void - Stream closed
Call .stop() to unsubscribe and close the stream.

streamSyslog

Stream syslog (batched lines every ~500ms).
const stream = client.streamSyslog();

stream.on('line', (line) => {
  console.log('[SYS]', line);
});

// Stop when done
stream.stop();
stream
LogStream
LogStream handle. See streamAppLog for event details.

Developer Tool Methods

simctl

Run simctl command targeting the instance with given arguments. Returns an EventEmitter that streams stdout, stderr, and exit events.
const execution = client.simctl(['boot']);

// Listen to raw data
execution.on('stdout', (data) => {
  console.log('stdout:', data.toString());
});

// Or listen line-by-line
execution.on('line-stdout', (line) => {
  console.log('Line:', line);
});

execution.on('line-stderr', (line) => {
  console.error('Error:', line);
});

execution.on('exit', (code) => {
  console.log('Process exited with code:', code);
});

// Or wait for completion
const result = await execution.wait();
console.log('Exit code:', result.code);
console.log('Full stdout:', result.stdout);

// Disconnect when command finishes
const execution2 = client.simctl(['status'], { disconnectOnExit: true });
args
string[]
required
Arguments to pass to simctl
options
object
execution
SimctlExecution
SimctlExecution handle with the following events:
  • stdout: (data: Buffer) => void
  • stderr: (data: Buffer) => void
  • line-stdout: (line: string) => void
  • line-stderr: (line: string) => void
  • exit: (code: number) => void
  • error: (error: Error) => void
Methods:
  • wait(): Promise that resolves with code, stdout, and stderr
  • stop(): void

xcrun

Run xcrun command with the given arguments. Returns the complete output once the command finishes (non-streaming).
// Get the SDK version for iphonesimulator
const result = await client.xcrun(['--sdk', 'iphonesimulator', '--show-sdk-version']);
console.log('SDK version:', result.stdout.trim());

// Get the SDK build version (default SDK)
const buildResult = await client.xcrun(['--show-sdk-build-version']);
console.log('Build version:', buildResult.stdout.trim());
Only the following flags are allowed:
  • --sdk <value>: Specify the SDK (e.g., ‘iphonesimulator’, ‘iphoneos’)
  • --show-sdk-version: Show the SDK version
  • --show-sdk-build-version: Show the SDK build version
  • --show-sdk-platform-version: Show the SDK platform version
args
string[]
required
Arguments to pass to xcrun
result
Promise<CommandResult>

xcodebuild

Run xcodebuild command with the given arguments. Only -version is allowed.
const result = await client.xcodebuild(['-version']);
console.log('Xcode version:', result.stdout);
// Output: Xcode 16.0
//         Build version 16A242d
args
string[]
required
Arguments to pass to xcodebuild (only ['-version'] is allowed)
result
Promise<CommandResult>
See xcrun for CommandResult properties

cp

Copy a file to the sandbox of the simulator. Returns the path of the file that can be used in simctl commands.
const remotePath = await client.cp('test-data.json', '/local/path/test-data.json');
console.log('File available at:', remotePath);
// Use remotePath in simctl commands
name
string
required
The name of the file in the sandbox of the simulator
path
string
required
The path of the file to copy to the sandbox
remotePath
Promise<string>
The path of the file that can be used in simctl commands

lsof

List all open files on the instance. Useful to start tunnel to the UNIX sockets listed here.
const files = await client.lsof();
for (const file of files) {
  console.log(file.path);
}
files
Promise<LsofEntry[]>

Connection Methods

disconnect

Disconnect from the Limrun instance.
client.disconnect();

getConnectionState

Get the current connection state.
const state = client.getConnectionState();
console.log(state); // 'connecting' | 'connected' | 'disconnected' | 'reconnecting'
state
ConnectionState
One of: 'connecting', 'connected', 'disconnected', 'reconnecting'

onConnectionStateChange

Register a callback for connection state changes.
const unsubscribe = client.onConnectionStateChange((state) => {
  console.log('Connection state changed:', state);
});

// Later, to unregister:
unsubscribe();
callback
ConnectionStateCallback
required
Callback function that receives the new connection state
unsubscribe
() => void
Function to unregister the callback

Complete Example

import { createInstanceClient } from '@limrun/ios-client';

async function automateApp() {
  // Create the client
  const client = await createInstanceClient({
    apiUrl: 'https://instance.limrun.com',
    token: 'your-token'
  });

  console.log('Connected to:', client.deviceInfo.model);

  // Install and launch app
  const { bundleId } = await client.installApp('https://example.com/app.ipa', {
    launchMode: 'RelaunchIfRunning'
  });

  console.log('App installed:', bundleId);

  // Wait for app to load
  await new Promise(resolve => setTimeout(resolve, 2000));

  // Take screenshot
  const screenshot = await client.screenshot();
  console.log(`Screenshot: ${screenshot.width}x${screenshot.height}`);

  // Interact with elements
  await client.tapElement({ accessibilityId: 'username-field' });
  await client.typeText('[email protected]');

  await client.tapElement({ accessibilityId: 'password-field' });
  await client.typeText('password123', true); // Press Enter after typing

  // Or tap by label
  await client.tapElement({ label: 'Sign In' });

  // Stream logs
  const logStream = client.streamAppLog(bundleId);
  logStream.on('line', (line) => {
    console.log('[LOG]', line);
  });

  // Wait for some activity
  await new Promise(resolve => setTimeout(resolve, 5000));

  // Cleanup
  logStream.stop();
  client.disconnect();
}

automateApp().catch(console.error);

Build docs developers (and LLMs) love