55import json
66from typing import Literal
77import asyncio
8+ import socket
89
910import requests
1011from fastapi import APIRouter
2425router = APIRouter (prefix = "/mobile" , tags = ["mobile" ])
2526
2627
28+ def is_wda_running (port : int ) -> bool :
29+ """Check if WebDriverAgent is running on given port."""
30+ try :
31+ response = requests .get (f"http://localhost:{ port } /status" , timeout = 1 )
32+ return response .status_code == 200
33+ except :
34+ return False
35+
36+
2737class InspectorResponse (BaseModel ):
2838 """Response model for the /inspector endpoint."""
29-
3039 status : Literal ["ok" , "error" ] = "ok"
3140 ui_xml : str | None = None
3241 screenshot : str | None = None # Base64 encoded image
@@ -136,46 +145,34 @@ def inspect(device_serial: str | None = None):
136145
137146@router .post ("/ios/start-services" )
138147def start_ios_services ():
139- """Start iOS services by calling launch_application function."""
140148 try :
141- from Framework .Built_In_Automation .Mobile .CrossPlatform .Appium .BuiltInFunctions import launch_application
142- from Framework .Built_In_Automation .Shared_Resources import BuiltInFunctionSharedResources as Shared_Resources
143-
144- # Get first booted iOS simulator
145149 ios_devices = get_ios_devices ()
146150 if not ios_devices :
147- return {"status" : "error" , "error" : "No iOS simulators available " }
151+ return {"status" : "error" , "error" : "No booted iOS simulators" }
148152
149153 device_udid = ios_devices [0 ].udid
150- device_name = ios_devices [0 ].name
151154
152- # Set up device_info with simulator details (this is what the server normally sends)
153- device_info = {
154- "device 1" : {
155- "id" : device_udid ,
156- "type" : "ios" ,
157- "imei" : "Simulated" ,
158- "model" : device_name ,
159- "osver" : "17.0"
160- }
161- }
155+ # Check if WDA is already running
156+ wda_port = 8100
157+ tries = 0
158+ while tries < 20 :
159+ if not is_wda_running (wda_port ):
160+ break
161+ wda_port += 2
162+ tries += 1
162163
163- # Set required shared variables
164- Shared_Resources .Set_Shared_Variables ("device_order" , None )
165- Shared_Resources .Set_Shared_Variables ("device_info" , device_info )
164+ if tries >= 20 :
165+ return {"status" : "error" , "error" : "No available WDA ports" }
166166
167- # Minimal dataset to trigger iOS launch
168- data_set = [
169- ("ios" , "element parameter" , "com.apple.Preferences" ),
170- ("action" , "action" , "launch" )
171- ]
167+ result = subprocess .run (
168+ ["xcrun" , "simctl" , "launch" , device_udid , "com.facebook.WebDriverAgentRunner.xctrunner" ],
169+ capture_output = True , text = True
170+ )
172171
173- result = launch_application (data_set )
172+ if result .returncode != 0 :
173+ return {"status" : "error" , "error" : f"Failed to launch WDA: { result .stderr } " }
174174
175- if result == "passed" :
176- return {"status" : "ok" , "message" : "iOS services started successfully" }
177- else :
178- return {"status" : "error" , "error" : "Failed to start iOS services" }
175+ return {"status" : "ok" , "port" : wda_port }
179176
180177 except Exception as e :
181178 return {"status" : "error" , "error" : str (e )}
@@ -185,7 +182,6 @@ def start_ios_services():
185182def inspect_ios (device_udid : str | None = None ):
186183 """Get iOS simulator screenshot and XML hierarchy."""
187184 try :
188- # Get first booted device if none specified
189185 if not device_udid :
190186 ios_devices = get_ios_devices ()
191187 if not ios_devices :
@@ -203,15 +199,12 @@ def inspect_ios(device_udid: str | None = None):
203199 )
204200 device_udid = booted_devices [0 ].udid
205201
206- # Capture UI and screenshot (same pattern as Android)
207202 capture_ios_ui_dump (device_udid )
208203 capture_ios_screenshot (device_udid )
209204
210- # Read XML file (same pattern as Android)
211205 with open (IOS_XML_PATH , 'r' , encoding = 'utf-8' ) as xml_file :
212206 xml_content = xml_file .read ()
213207
214- # Read and encode screenshot
215208 with open (IOS_SCREENSHOT_PATH , 'rb' ) as img_file :
216209 screenshot_bytes = img_file .read ()
217210 screenshot_base64 = base64 .b64encode (screenshot_bytes ).decode ('utf-8' )
@@ -227,6 +220,7 @@ def inspect_ios(device_udid: str | None = None):
227220 error = str (e )
228221 )
229222
223+
230224@router .get ("/dump/driver" )
231225def dump_driver ():
232226 """Dump the current driver."""
@@ -287,12 +281,9 @@ def capture_screenshot(device_serial: str | None = None):
287281
288282
289283def capture_ios_screenshot (device_udid : str ):
290- """Capture screenshot from iOS simulator."""
291284 try :
292- # Use absolute path
293285 screenshot_path = os .path .abspath (IOS_SCREENSHOT_PATH )
294286
295- # Remove existing file if it exists
296287 if os .path .exists (screenshot_path ):
297288 os .remove (screenshot_path )
298289
@@ -301,7 +292,6 @@ def capture_ios_screenshot(device_udid: str):
301292 capture_output = True , text = True , check = True
302293 )
303294
304- # Verify file was created
305295 if not os .path .exists (screenshot_path ):
306296 raise Exception ("Screenshot file was not created" )
307297
@@ -313,21 +303,24 @@ def capture_ios_screenshot(device_udid: str):
313303
314304
315305def get_real_ios_hierarchy (device_udid : str ):
316- """Try to get real iOS hierarchy using Appium/WebDriverAgent."""
317306 try :
318307 import requests
319- wda_ports = [8100 , 8101 , 8102 ]
320308
321- for port in wda_ports :
309+ wda_port = 8100
310+ tries = 0
311+
312+ while tries < 20 :
322313 try :
323- wda_url = f"http://localhost:{ port } "
314+ wda_url = f"http://localhost:{ wda_port } "
324315
325316 # Quick status check
326317 status_response = requests .get (f"{ wda_url } /status" , timeout = 1 )
327318 if status_response .status_code != 200 :
319+ wda_port += 2
320+ tries += 1
328321 continue
329322
330- # Try existing sessions first
323+ # existing sessions first
331324 sessions_response = requests .get (f"{ wda_url } /sessions" , timeout = 1 )
332325 if sessions_response .status_code == 200 :
333326 sessions = sessions_response .json ()
@@ -337,12 +330,14 @@ def get_real_ios_hierarchy(device_udid: str):
337330 if source_response .status_code == 200 :
338331 return source_response .text
339332
340- # Try direct source
333+ # direct source
341334 source_response = requests .get (f"{ wda_url } /source" , timeout = 2 )
342335 if source_response .status_code == 200 :
343336 return source_response .text
344337
345338 except :
339+ wda_port += 2
340+ tries += 1
346341 continue
347342
348343 except :
@@ -352,11 +347,8 @@ def get_real_ios_hierarchy(device_udid: str):
352347
353348
354349def capture_ios_ui_dump (device_udid : str ):
355- """Capture the current UI hierarchy from iOS device (same pattern as Android)"""
356- # Try WebDriverAgent first (real hierarchy like Android's uiautomator)
357350 real_hierarchy = get_real_ios_hierarchy (device_udid )
358351 if real_hierarchy :
359- # Extract XML from JSON wrapper if needed
360352 try :
361353 import json
362354 json_data = json .loads (real_hierarchy )
@@ -368,7 +360,7 @@ def capture_ios_ui_dump(device_udid: str):
368360 xml_file .write (xml_content )
369361 return
370362
371- # Fallback to Appium driver (same as Android fallback)
363+ # Fallback to Appium driver
372364 try :
373365 from Framework .Built_In_Automation .Mobile .CrossPlatform .Appium .BuiltInFunctions import appium_driver
374366 if appium_driver is not None :
@@ -380,7 +372,7 @@ def capture_ios_ui_dump(device_udid: str):
380372 pass
381373
382374 # No real source available
383- raise Exception ("No iOS UI hierarchy source available . Please start WebDriverAgent (port 8100) or Appium server (port 4723) ." )
375+ raise Exception ("iOS service error . Please reload the iOS inspector page or run a test case ." )
384376
385377
386378async def upload_android_ui_dump ():
@@ -458,4 +450,4 @@ async def upload_ios_ui_dump():
458450 CommonUtil .ExecLog ("" , "UI dump uploaded successfully" , iLogLevel = 1 )
459451 except Exception as e :
460452 CommonUtil .ExecLog ("" , f"Error uploading iOS UI dump: { str (e )} " , iLogLevel = 3 )
461- await asyncio .sleep (5 )
453+ await asyncio .sleep (5 )
0 commit comments