Remote control of iOS Safari Web Inspector.
Works via socket, injecting commands following WIR (WebKit Inspector Remote) -
an internal yet not secret protocol that mimics CDP.
Less brittle than Appium, no dependencies.
-
Boot simulator and open page:
xcrun simctl boot <UDID> open -a Simulator xcrun simctl openurl booted "https://example.com"
-
Find the socket:
find /private/tmp -name "*webinspectord_sim*" 2>/dev/null
-
Connect and send commands using WIR Protocol
-
Execute JS using Runtime.evaluate
Binary plist messages over Unix socket with 4-byte big-endian length prefix.
Connect:
import socket, struct, plistlib
SOCK = "/private/tmp/com.apple.launchd.xxx/com.apple.webinspectord_sim.socket"
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
sock.connect(SOCK)
sock.settimeout(2.0)
def send(msg):
data = plistlib.dumps(msg, fmt=plistlib.FMT_BINARY)
sock.sendall(struct.pack('>I', len(data)) + data)Handshake sequence:
# 1. Register connection
send({'__selector': '_rpc_reportIdentifier:',
'__argument': {'WIRConnectionIdentifierKey': 'my-client'}})
# 2. Get apps
send({'__selector': '_rpc_getConnectedApplications:', '__argument': {}})
# 3. Get pages (after finding app_id from response)
send({'__selector': '_rpc_forwardGetListing:',
'__argument': {'WIRConnectionIdentifierKey': 'my-client',
'WIRApplicationIdentifierKey': app_id}})
# 4. Setup inspector socket (after finding page_id)
send({'__selector': '_rpc_forwardSocketSetup:',
'__argument': {'WIRConnectionIdentifierKey': 'my-client',
'WIRApplicationIdentifierKey': app_id,
'WIRPageIdentifierKey': page_id,
'WIRSenderKey': sender_id,
'WIRAutomaticallyPause': False}})Execute JS in the browser context via CDP wrapped in WIR.
import json
def evaluate(expr):
# If target_id exists, wrap in Target.sendMessageToTarget
inner = json.dumps({
'id': msg_id,
'method': 'Runtime.evaluate',
'params': {'expression': expr, 'returnByValue': True}
})
socket_data = json.dumps({
'id': outer_id,
'method': 'Target.sendMessageToTarget',
'params': {'targetId': target_id, 'message': inner}
}).encode()
send({
'__selector': '_rpc_forwardSocketData:',
'__argument': {
'WIRConnectionIdentifierKey': 'my-client',
'WIRApplicationIdentifierKey': app_id,
'WIRPageIdentifierKey': page_id,
'WIRSenderKey': sender_id,
'WIRSocketDataKey': socket_data
}
})Path changes on restart; always find, never hardcode.
Web Inspector not enabled: Settings → Safari → Advanced → Web Inspector ON
Page still loading, wait 10-15s and retry.
Extract from Target.targetCreated event in setup response; needed to wrap
CDP commands in Target.sendMessageToTarget.