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 Show InstanceClientOptions properties
The API URL for the instance
The token to use for authentication
Controls logging verbosity. One of: 'none', 'error', 'warn', 'info', 'debug'
Maximum number of reconnection attempts
Initial reconnection delay in milliseconds
Maximum reconnection delay in milliseconds
Returns
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'
// }
Show DeviceInfo properties
Screen width in points (Swift Double)
Screen height in points (Swift Double)
Device model name (e.g., “iPhone 14 Pro”)
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' );
Show ScreenshotData properties
Base64-encoded JPEG image data
Width in points (for tap coordinates)
Height in points (for tap coordinates)
tap
Tap at the specified coordinates using the device’s native screen dimensions.
await client . tap ( 195 , 422 );
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 coordinate in the provided screen coordinate space
Y coordinate in the provided screen coordinate space
Width of the coordinate space
Height of the coordinate space
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
Total pixels to scroll (finger movement distance)
Starting coordinate [x, y]. Defaults to screen center.
0.0-1.0 controlling scroll speed and inertia. 0 (default) = slow scroll, no momentum. 1 = fastest with max inertia.
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 });
Optional point to get the element at that specific location Show AccessibilityPoint properties
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. Show AccessibilitySelector properties
Match by AXUniqueId (accessibilityIdentifier) - exact match
Match by AXLabel - exact match
Match by AXLabel - contains (case-insensitive)
Match by element type/role (e.g., “Button”, “TextField”) - case-insensitive
Match by title - exact match
Match by title - contains (case-insensitive)
Match by AXValue - exact match
result
Promise<TapElementResult>
Show TapElementResult properties
Label of the tapped element
Type of the tapped element
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
Show ElementResult properties
Label of the modified element
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
Show ElementResult properties
Label of the modified element
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'
});
selector
AccessibilitySelector
required
The selector criteria to find the element
Show ElementResult properties
Label of the modified element
typeText
Type text into the currently focused input field.
await client . typeText ( 'Hello, World!' );
// Type and press Enter
await client . typeText ( 'search query' , true );
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' ]);
The key to press (e.g., ‘a’, ‘enter’, ‘backspace’, ‘up’, ‘f1’)
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' );
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' );
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'
});
The URL to download the app from
Show AppInstallationOptions properties
MD5 hash for caching - if provided and matches cached version, skips download
launchMode
'ForegroundIfRunning' | 'RelaunchIfRunning'
Launch mode after installation:
'ForegroundIfRunning': Bring to foreground if already running, otherwise launch
'RelaunchIfRunning': Kill and relaunch if already running
undefined: Don’t launch after installation
result
Promise<AppInstallationResult>
Show AppInstallationResult properties
The URL the app was installed from
Bundle ID of the installed app
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 } ` );
}
Show InstalledApp properties
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
});
Path to the local .app bundle
Whether to install the app after syncing
Maximum patch size in bytes before falling back to full upload
launchMode
'ForegroundIfRunning' | 'RelaunchIfRunning'
Launch mode after installation
Enable watch mode for automatic re-sync on changes
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' );
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 );
Bundle identifier of the app
Number of lines to return (clamped to server limit)
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 ();
Bundle identifier of the app
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 ();
LogStream handle. See streamAppLog for event details.
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 });
Arguments to pass to simctl
If true, disconnect from the instance when the command completes
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
Arguments to pass to xcrun
Show CommandResult properties
Standard output from the command
Standard error from the command
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
Arguments to pass to xcodebuild (only ['-version'] is allowed)
See xcrun for CommandResult properties
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
The name of the file in the sandbox of the simulator
The path of the file to copy to the sandbox
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 );
}
Show LsofEntry properties
Connection Methods
disconnect
Disconnect from the Limrun instance.
getConnectionState
Get the current connection state.
const state = client . getConnectionState ();
console . log ( state ); // 'connecting' | 'connected' | 'disconnected' | 'reconnecting'
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
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 );