From d7be8c90a67db47669502567f9d0343d36e89451 Mon Sep 17 00:00:00 2001 From: shakib Date: Mon, 8 Dec 2025 19:21:37 +0600 Subject: [PATCH 1/9] added device detection & screenshot take functionality --- server/mobile.py | 96 ++++++++++++++++++++++++++++++- test_ios_detection.py | 129 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 223 insertions(+), 2 deletions(-) create mode 100644 test_ios_detection.py diff --git a/server/mobile.py b/server/mobile.py index 2f04967c..b82adedd 100644 --- a/server/mobile.py +++ b/server/mobile.py @@ -2,6 +2,7 @@ import os import subprocess import base64 +import json from typing import Literal import asyncio @@ -14,6 +15,7 @@ ADB_PATH = "adb" # Ensure ADB is in PATH UI_XML_PATH = "ui.xml" SCREENSHOT_PATH = "screen.png" +IOS_SCREENSHOT_PATH = "ios_screen.png" router = APIRouter(prefix="/mobile", tags=["mobile"]) @@ -32,8 +34,15 @@ class DeviceInfo(BaseModel): serial: str status: str name: str | None = None - # model: str | None = None - # product: str | None = None + + +class IOSDeviceInfo(BaseModel): + """Model for iOS device information.""" + udid: str + name: str + state: str + runtime: str + device_type: str @router.get("/devices", response_model=list[DeviceInfo]) def get_devices(): @@ -63,6 +72,34 @@ def get_devices(): return [] +@router.get("/ios/devices", response_model=list[IOSDeviceInfo]) +def get_ios_devices(): + """Get list of available iOS simulators.""" + try: + result = subprocess.run( + ["xcrun", "simctl", "list", "devices", "-j"], + capture_output=True, text=True, check=True + ) + + devices_data = json.loads(result.stdout) + ios_devices = [] + + for runtime, devices in devices_data.get("devices", {}).items(): + for device in devices: + if device.get("isAvailable", False): + ios_devices.append(IOSDeviceInfo( + udid=device["udid"], + name=device["name"], + state=device["state"], + runtime=runtime, + device_type=device.get("deviceTypeIdentifier", "Unknown") + )) + + return ios_devices + except Exception as e: + return [] + + @router.get("/inspect") def inspect(device_serial: str | None = None): """Get the Mobile DOM and screenshot.""" @@ -91,6 +128,40 @@ def inspect(device_serial: str | None = None): error=str(e) ) + +@router.get("/ios/inspect") +def inspect_ios(device_udid: str | None = None): + """Get iOS simulator screenshot and XML hierarchy.""" + try: + # Get first available device if none specified + if not device_udid: + ios_devices = get_ios_devices() + if not ios_devices: + return InspectorResponse( + status="error", + error="No iOS simulators available" + ) + device_udid = ios_devices[0].udid + + # Capture screenshot + capture_ios_screenshot(device_udid) + + # Read and encode screenshot + with open(IOS_SCREENSHOT_PATH, 'rb') as img_file: + screenshot_bytes = img_file.read() + screenshot_base64 = base64.b64encode(screenshot_bytes).decode('utf-8') + + return InspectorResponse( + status="ok", + ui_xml=None, # XML hierarchy will be implemented later + screenshot=screenshot_base64 + ) + except Exception as e: + return InspectorResponse( + status="error", + error=str(e) + ) + @router.get("/dump/driver") def dump_driver(): """Dump the current driver.""" @@ -150,6 +221,27 @@ def capture_screenshot(device_serial: str | None = None): return +def capture_ios_screenshot(device_udid: str): + """Capture screenshot from iOS simulator.""" + try: + result = subprocess.run( + ["xcrun", "simctl", "io", device_udid, "screenshot", IOS_SCREENSHOT_PATH], + capture_output=True, text=True, check=True + ) + return True + except subprocess.CalledProcessError as e: + raise Exception(f"Failed to capture iOS screenshot: {e.stderr}") + + +def run_xcrun_command(command): + """Run an xcrun command and return the output.""" + try: + result = subprocess.run(command, shell=True, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) + return result.stdout.strip() + except subprocess.CalledProcessError as e: + return f"Error: {e.stderr.strip()}" + + async def upload_android_ui_dump(): prev_xml_hash = "" while True: diff --git a/test_ios_detection.py b/test_ios_detection.py new file mode 100644 index 00000000..49b2ae23 --- /dev/null +++ b/test_ios_detection.py @@ -0,0 +1,129 @@ +#!/usr/bin/env python3 +""" +Test script to verify iOS device detection functionality +""" +import subprocess +import json +import sys + +def test_xcrun_availability(): + """Test if xcrun command is available""" + try: + result = subprocess.run(["xcrun", "--version"], capture_output=True, text=True, check=True) + print("✅ xcrun is available") + print(f"Version: {result.stdout.strip()}") + return True + except (subprocess.CalledProcessError, FileNotFoundError) as e: + print("❌ xcrun is not available") + print(f"Error: {e}") + return False + +def test_simctl_list(): + """Test if simctl list devices works""" + try: + result = subprocess.run( + ["xcrun", "simctl", "list", "devices", "-j"], + capture_output=True, text=True, check=True + ) + print("✅ simctl list devices works") + + devices_data = json.loads(result.stdout) + print(f"Found {len(devices_data.get('devices', {}))} runtime categories") + + total_devices = 0 + available_devices = 0 + + for runtime, devices in devices_data.get("devices", {}).items(): + runtime_available = 0 + for device in devices: + total_devices += 1 + if device.get("isAvailable", False): + available_devices += 1 + runtime_available += 1 + + if runtime_available > 0: + print(f" {runtime}: {runtime_available} available devices") + + print(f"Total devices: {total_devices}") + print(f"Available devices: {available_devices}") + + return available_devices > 0 + + except (subprocess.CalledProcessError, FileNotFoundError, json.JSONDecodeError) as e: + print("❌ simctl list devices failed") + print(f"Error: {e}") + return False + +def test_screenshot_capability(): + """Test if we can take a screenshot from the first available device""" + try: + # Get available devices + result = subprocess.run( + ["xcrun", "simctl", "list", "devices", "-j"], + capture_output=True, text=True, check=True + ) + + devices_data = json.loads(result.stdout) + first_device = None + + for runtime, devices in devices_data.get("devices", {}).items(): + for device in devices: + if device.get("isAvailable", False) and device.get("state") == "Booted": + first_device = device + break + if first_device: + break + + if not first_device: + print("⚠️ No booted iOS simulators found. Please start an iOS simulator first.") + return False + + print(f"Testing screenshot with device: {first_device['name']} ({first_device['udid']})") + + # Try to take a screenshot + result = subprocess.run( + ["xcrun", "simctl", "io", first_device["udid"], "screenshot", "test_screenshot.png"], + capture_output=True, text=True, check=True + ) + + print("✅ Screenshot capability works") + print("Screenshot saved as test_screenshot.png") + return True + + except (subprocess.CalledProcessError, FileNotFoundError, json.JSONDecodeError) as e: + print("❌ Screenshot test failed") + print(f"Error: {e}") + return False + +def main(): + """Run all tests""" + print("Testing iOS Device Detection and Screenshot Capability") + print("=" * 60) + + tests = [ + ("xcrun availability", test_xcrun_availability), + ("simctl device listing", test_simctl_list), + ("screenshot capability", test_screenshot_capability), + ] + + passed = 0 + total = len(tests) + + for test_name, test_func in tests: + print(f"\n🧪 Testing {test_name}...") + if test_func(): + passed += 1 + print() + + print("=" * 60) + print(f"Tests passed: {passed}/{total}") + + if passed == total: + print("🎉 All tests passed! iOS device detection should work.") + return 0 + else: + print("⚠️ Some tests failed. Please check the requirements.") + return 1 + +if __name__ == "__main__": + sys.exit(main()) From adf6a294da6c838f558e1dc33f2d182fcd7da931 Mon Sep 17 00:00:00 2001 From: shakib Date: Tue, 9 Dec 2025 21:36:00 +0600 Subject: [PATCH 2/9] test server script added --- test_ios_server.py | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 test_ios_server.py diff --git a/test_ios_server.py b/test_ios_server.py new file mode 100644 index 00000000..4db5fa9f --- /dev/null +++ b/test_ios_server.py @@ -0,0 +1,32 @@ +#!/usr/bin/env python3 +""" +Simple test script to verify iOS endpoints work +""" +import sys +import os +sys.path.append(os.path.dirname(__file__)) + +from server.mobile import get_ios_devices, inspect_ios + +def test_ios_endpoints(): + print("Testing iOS endpoints...") + + # Test device listing + print("\n1. Testing get_ios_devices():") + devices = get_ios_devices() + print(f"Found {len(devices)} devices:") + for device in devices: + print(f" - {device.name} ({device.udid})") + + # Test screenshot capture + print("\n2. Testing inspect_ios():") + result = inspect_ios() + print(f"Status: {result.status}") + if result.error: + print(f"Error: {result.error}") + else: + print(f"Screenshot length: {len(result.screenshot) if result.screenshot else 0} characters") + print(f"XML available: {'Yes' if result.ui_xml else 'No'}") + +if __name__ == "__main__": + test_ios_endpoints() From 412202bd8e9e8c6828a6e20d48314b8fc0402abe Mon Sep 17 00:00:00 2001 From: mdshakib007 Date: Wed, 10 Dec 2025 17:47:09 +0600 Subject: [PATCH 3/9] screenshot path updated --- server/mobile.py | 30 ++++++++++++++++++++++++++---- test_ios_detection.py | 22 +++++++++------------- 2 files changed, 35 insertions(+), 17 deletions(-) diff --git a/server/mobile.py b/server/mobile.py index b82adedd..8d8036b7 100644 --- a/server/mobile.py +++ b/server/mobile.py @@ -15,7 +15,7 @@ ADB_PATH = "adb" # Ensure ADB is in PATH UI_XML_PATH = "ui.xml" SCREENSHOT_PATH = "screen.png" -IOS_SCREENSHOT_PATH = "ios_screen.png" +IOS_SCREENSHOT_PATH = os.path.join(os.path.dirname(os.path.dirname(__file__)), "ios_screen.png") router = APIRouter(prefix="/mobile", tags=["mobile"]) @@ -133,7 +133,7 @@ def inspect(device_serial: str | None = None): def inspect_ios(device_udid: str | None = None): """Get iOS simulator screenshot and XML hierarchy.""" try: - # Get first available device if none specified + # Get first booted device if none specified if not device_udid: ios_devices = get_ios_devices() if not ios_devices: @@ -141,7 +141,15 @@ def inspect_ios(device_udid: str | None = None): status="error", error="No iOS simulators available" ) - device_udid = ios_devices[0].udid + + # Find first booted device + booted_devices = [d for d in ios_devices if d.state == "Booted"] + if not booted_devices: + return InspectorResponse( + status="error", + error="No booted iOS simulators found. Please start an iOS simulator." + ) + device_udid = booted_devices[0].udid # Capture screenshot capture_ios_screenshot(device_udid) @@ -224,13 +232,27 @@ def capture_screenshot(device_serial: str | None = None): def capture_ios_screenshot(device_udid: str): """Capture screenshot from iOS simulator.""" try: + # Use absolute path + screenshot_path = os.path.abspath(IOS_SCREENSHOT_PATH) + + # Remove existing file if it exists + if os.path.exists(screenshot_path): + os.remove(screenshot_path) + result = subprocess.run( - ["xcrun", "simctl", "io", device_udid, "screenshot", IOS_SCREENSHOT_PATH], + ["xcrun", "simctl", "io", device_udid, "screenshot", "--type=png", screenshot_path], capture_output=True, text=True, check=True ) + + # Verify file was created + if not os.path.exists(screenshot_path): + raise Exception("Screenshot file was not created") + return True except subprocess.CalledProcessError as e: raise Exception(f"Failed to capture iOS screenshot: {e.stderr}") + except Exception as e: + raise Exception(f"Failed to capture iOS screenshot: {str(e)}") def run_xcrun_command(command): diff --git a/test_ios_detection.py b/test_ios_detection.py index 49b2ae23..0e0047f4 100644 --- a/test_ios_detection.py +++ b/test_ios_detection.py @@ -1,7 +1,3 @@ -#!/usr/bin/env python3 -""" -Test script to verify iOS device detection functionality -""" import subprocess import json import sys @@ -10,11 +6,11 @@ def test_xcrun_availability(): """Test if xcrun command is available""" try: result = subprocess.run(["xcrun", "--version"], capture_output=True, text=True, check=True) - print("✅ xcrun is available") + print("xcrun is available") print(f"Version: {result.stdout.strip()}") return True except (subprocess.CalledProcessError, FileNotFoundError) as e: - print("❌ xcrun is not available") + print("xcrun is not available") print(f"Error: {e}") return False @@ -25,7 +21,7 @@ def test_simctl_list(): ["xcrun", "simctl", "list", "devices", "-j"], capture_output=True, text=True, check=True ) - print("✅ simctl list devices works") + print("simctl list devices works") devices_data = json.loads(result.stdout) print(f"Found {len(devices_data.get('devices', {}))} runtime categories") @@ -50,7 +46,7 @@ def test_simctl_list(): return available_devices > 0 except (subprocess.CalledProcessError, FileNotFoundError, json.JSONDecodeError) as e: - print("❌ simctl list devices failed") + print("simctl list devices failed") print(f"Error: {e}") return False @@ -75,7 +71,7 @@ def test_screenshot_capability(): break if not first_device: - print("⚠️ No booted iOS simulators found. Please start an iOS simulator first.") + print("No booted iOS simulators found. Please start an iOS simulator first.") return False print(f"Testing screenshot with device: {first_device['name']} ({first_device['udid']})") @@ -86,12 +82,12 @@ def test_screenshot_capability(): capture_output=True, text=True, check=True ) - print("✅ Screenshot capability works") + print("Screenshot capability works") print("Screenshot saved as test_screenshot.png") return True except (subprocess.CalledProcessError, FileNotFoundError, json.JSONDecodeError) as e: - print("❌ Screenshot test failed") + print("Screenshot test failed") print(f"Error: {e}") return False @@ -119,10 +115,10 @@ def main(): print(f"Tests passed: {passed}/{total}") if passed == total: - print("🎉 All tests passed! iOS device detection should work.") + print("All tests passed! iOS device detection should work.") return 0 else: - print("⚠️ Some tests failed. Please check the requirements.") + print("Some tests failed. Please check the requirements.") return 1 if __name__ == "__main__": From 85ca7c87560d30f3ca5c7d7e8ca796e064000236 Mon Sep 17 00:00:00 2001 From: mdshakib007 Date: Sun, 14 Dec 2025 13:35:54 +0600 Subject: [PATCH 4/9] now node sent the ui xml to server and server shows --- node_cli.py | 3 +- server/mobile.py | 134 ++++++++++++++++++++++++++++++--- test_ios_hierarchy.py | 155 +++++++++++++++++++++++++++++++++++++++ test_ios_upload.py | 23 ++++++ test_ios_upload_debug.py | 70 ++++++++++++++++++ 5 files changed, 373 insertions(+), 12 deletions(-) create mode 100644 test_ios_hierarchy.py create mode 100644 test_ios_upload.py create mode 100644 test_ios_upload_debug.py diff --git a/node_cli.py b/node_cli.py index 9e9bc6f7..9958dae8 100755 --- a/node_cli.py +++ b/node_cli.py @@ -37,7 +37,7 @@ from cryptography.hazmat.primitives import serialization from settings import ZEUZ_NODE_PRIVATE_RSA_KEYS_DIR from Framework.install_handler.long_poll_handler import InstallHandler -from server.mobile import upload_android_ui_dump +from server.mobile import upload_android_ui_dump, upload_ios_ui_dump def adjust_python_path(): @@ -1341,6 +1341,7 @@ async def main(): update_outdated_modules() asyncio.create_task(start_server()) asyncio.create_task(upload_android_ui_dump()) + asyncio.create_task(upload_ios_ui_dump()) asyncio.create_task(delete_old_automationlog_folders()) await destroy_session() diff --git a/server/mobile.py b/server/mobile.py index 8d8036b7..024c1bfe 100644 --- a/server/mobile.py +++ b/server/mobile.py @@ -15,7 +15,8 @@ ADB_PATH = "adb" # Ensure ADB is in PATH UI_XML_PATH = "ui.xml" SCREENSHOT_PATH = "screen.png" -IOS_SCREENSHOT_PATH = os.path.join(os.path.dirname(os.path.dirname(__file__)), "ios_screen.png") +IOS_SCREENSHOT_PATH = "ios_screen.png" +IOS_XML_PATH = "ios_ui.xml" router = APIRouter(prefix="/mobile", tags=["mobile"]) @@ -74,7 +75,7 @@ def get_devices(): @router.get("/ios/devices", response_model=list[IOSDeviceInfo]) def get_ios_devices(): - """Get list of available iOS simulators.""" + """Get list of booted iOS simulators only.""" try: result = subprocess.run( ["xcrun", "simctl", "list", "devices", "-j"], @@ -86,7 +87,8 @@ def get_ios_devices(): for runtime, devices in devices_data.get("devices", {}).items(): for device in devices: - if device.get("isAvailable", False): + # Only return booted devices + if device.get("isAvailable", False) and device.get("state") == "Booted": ios_devices.append(IOSDeviceInfo( udid=device["udid"], name=device["name"], @@ -151,9 +153,14 @@ def inspect_ios(device_udid: str | None = None): ) device_udid = booted_devices[0].udid - # Capture screenshot + # Capture UI and screenshot (same pattern as Android) + capture_ios_ui_dump(device_udid) capture_ios_screenshot(device_udid) + # Read XML file (same pattern as Android) + with open(IOS_XML_PATH, 'r', encoding='utf-8') as xml_file: + xml_content = xml_file.read() + # Read and encode screenshot with open(IOS_SCREENSHOT_PATH, 'rb') as img_file: screenshot_bytes = img_file.read() @@ -161,7 +168,7 @@ def inspect_ios(device_udid: str | None = None): return InspectorResponse( status="ok", - ui_xml=None, # XML hierarchy will be implemented later + ui_xml=xml_content, screenshot=screenshot_base64 ) except Exception as e: @@ -255,13 +262,75 @@ def capture_ios_screenshot(device_udid: str): raise Exception(f"Failed to capture iOS screenshot: {str(e)}") -def run_xcrun_command(command): - """Run an xcrun command and return the output.""" +def get_real_ios_hierarchy(device_udid: str): + """Try to get real iOS hierarchy using Appium/WebDriverAgent.""" try: - result = subprocess.run(command, shell=True, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) - return result.stdout.strip() - except subprocess.CalledProcessError as e: - return f"Error: {e.stderr.strip()}" + import requests + wda_ports = [8100, 8101, 8102] + + for port in wda_ports: + try: + wda_url = f"http://localhost:{port}" + + # Quick status check + status_response = requests.get(f"{wda_url}/status", timeout=1) + if status_response.status_code != 200: + continue + + # Try existing sessions first + sessions_response = requests.get(f"{wda_url}/sessions", timeout=1) + if sessions_response.status_code == 200: + sessions = sessions_response.json() + if sessions and len(sessions) > 0: + session_id = sessions[0]['id'] + source_response = requests.get(f"{wda_url}/session/{session_id}/source", timeout=3) + if source_response.status_code == 200: + return source_response.text + + # Try direct source + source_response = requests.get(f"{wda_url}/source", timeout=2) + if source_response.status_code == 200: + return source_response.text + + except: + continue + + except: + pass + + return None + + +def capture_ios_ui_dump(device_udid: str): + """Capture the current UI hierarchy from iOS device (same pattern as Android)""" + # Try WebDriverAgent first (real hierarchy like Android's uiautomator) + real_hierarchy = get_real_ios_hierarchy(device_udid) + if real_hierarchy: + # Extract XML from JSON wrapper if needed + try: + import json + json_data = json.loads(real_hierarchy) + xml_content = json_data.get("value", real_hierarchy) + except: + xml_content = real_hierarchy + + with open(IOS_XML_PATH, 'w', encoding='utf-8') as xml_file: + xml_file.write(xml_content) + return + + # Fallback to Appium driver (same as Android fallback) + try: + from Framework.Built_In_Automation.Mobile.CrossPlatform.Appium.BuiltInFunctions import appium_driver + if appium_driver is not None: + page_src = appium_driver.page_source + with open(IOS_XML_PATH, 'w', encoding='utf-8') as xml_file: + xml_file.write(page_src) + return + except: + pass + + # No real source available + raise Exception("No iOS UI hierarchy source available. Please start WebDriverAgent (port 8100) or Appium server (port 4723).") async def upload_android_ui_dump(): @@ -297,3 +366,46 @@ async def upload_android_ui_dump(): except Exception as e: CommonUtil.ExecLog("", f"Error uploading UI dump: {str(e)}", iLogLevel=3) await asyncio.sleep(5) + + +async def upload_ios_ui_dump(): + prev_xml_hash = "" + while True: + try: + ios_devices = get_ios_devices() + if not ios_devices: + await asyncio.sleep(5) + continue + + device_udid = ios_devices[0].udid + capture_ios_ui_dump(device_udid) + + try: + with open(IOS_XML_PATH, 'r', encoding='utf-8') as xml_file: + xml_content = xml_file.read() + xml_content = xml_content.replace("", "", 1) + new_xml_hash = hashlib.sha256(xml_content.encode('utf-8')).hexdigest() + # Don't upload if the content hasn't changed + if prev_xml_hash == new_xml_hash: + await asyncio.sleep(5) + continue + prev_xml_hash = new_xml_hash + + except FileNotFoundError: + await asyncio.sleep(5) + continue + + url = ConfigModule.get_config_value("Authentication", "server_address").strip() + "/node_ai_contents/" + apiKey = ConfigModule.get_config_value("Authentication", "api-key").strip() + res = requests.post( + url, + headers={"X-Api-Key": apiKey}, + json={ + "dom_mob": {"dom": xml_content}, + "node_id": CommonUtil.MachineInfo().getLocalUser().lower() + }) + if res.ok: + CommonUtil.ExecLog("", "UI dump uploaded successfully", iLogLevel=1) + except Exception as e: + CommonUtil.ExecLog("", f"Error uploading iOS UI dump: {str(e)}", iLogLevel=3) + await asyncio.sleep(5) diff --git a/test_ios_hierarchy.py b/test_ios_hierarchy.py new file mode 100644 index 00000000..8146eaf3 --- /dev/null +++ b/test_ios_hierarchy.py @@ -0,0 +1,155 @@ +#!/usr/bin/env python3 +""" +Test script to check all available iOS hierarchy extraction methods +""" +import requests +import subprocess +import json + +def test_webdriver_agent(): + """Test WebDriverAgent on common ports""" + print("🧪 Testing WebDriverAgent...") + ports = [8100, 8101, 8102] + + for port in ports: + try: + url = f"http://localhost:{port}" + print(f" Trying port {port}...") + + # Check status + response = requests.get(f"{url}/status", timeout=2) + if response.status_code == 200: + print(f" ✅ WebDriverAgent running on port {port}") + + # Check sessions + sessions = requests.get(f"{url}/sessions", timeout=2) + if sessions.status_code == 200: + session_data = sessions.json() + print(f" Sessions: {len(session_data)}") + + if session_data: + session_id = session_data[0]['id'] + source = requests.get(f"{url}/session/{session_id}/source", timeout=3) + if source.status_code == 200: + print(f" ✅ Got XML hierarchy ({len(source.text)} chars)") + return source.text + + # Try direct source + source = requests.get(f"{url}/source", timeout=2) + if source.status_code == 200: + print(f" ✅ Got XML hierarchy via direct source ({len(source.text)} chars)") + return source.text + + except Exception as e: + print(f" ❌ Port {port}: {e}") + + print(" ❌ WebDriverAgent not found") + return None + +def test_appium_server(): + """Test Appium server""" + print("\n🧪 Testing Appium Server...") + try: + response = requests.get("http://localhost:4723/status", timeout=2) + if response.status_code == 200: + print(" ✅ Appium server running on port 4723") + return True + else: + print(" ❌ Appium server not responding") + except Exception as e: + print(f" ❌ Appium server: {e}") + return False + +def test_xcrun_accessibility(): + """Test xcrun accessibility methods""" + print("\n🧪 Testing xcrun accessibility methods...") + + # Get booted device + try: + result = subprocess.run( + ["xcrun", "simctl", "list", "devices", "-j"], + capture_output=True, text=True, check=True + ) + devices_data = json.loads(result.stdout) + booted_device = None + + for runtime, devices in devices_data.get("devices", {}).items(): + for device in devices: + if device.get("state") == "Booted": + booted_device = device["udid"] + print(f" Found booted device: {device['name']} ({booted_device})") + break + if booted_device: + break + + if not booted_device: + print(" ❌ No booted iOS simulator found") + return None + + # Try accessibility inspector + print(" Trying accessibility methods...") + + # Method 1: Try to enable accessibility + try: + subprocess.run([ + "xcrun", "simctl", "spawn", booted_device, + "defaults", "write", "com.apple.Accessibility", "ApplicationAccessibilityEnabled", "-bool", "true" + ], capture_output=True, timeout=5) + print(" ✅ Accessibility enabled") + except: + print(" ⚠️ Could not enable accessibility") + + # Method 2: Try to get accessibility tree (this usually doesn't work without additional tools) + print(" ❌ xcrun doesn't provide direct UI hierarchy access") + + except Exception as e: + print(f" ❌ xcrun accessibility: {e}") + + return None + +def main(): + print("iOS UI Hierarchy Extraction Test") + print("=" * 50) + + # Test all methods + wda_xml = test_webdriver_agent() + appium_available = test_appium_server() + xcrun_result = test_xcrun_accessibility() + + print("\n📋 Summary:") + print("=" * 50) + + if wda_xml: + print("✅ RECOMMENDED: WebDriverAgent is working - use this for real hierarchy") + # Save sample + with open("sample_ios_hierarchy.xml", "w") as f: + f.write(wda_xml) + print(" Sample saved to: sample_ios_hierarchy.xml") + else: + print("❌ WebDriverAgent not available") + print("\n🔧 To set up WebDriverAgent:") + print("1. Clone: git clone https://github.com/appium/WebDriverAgent.git") + print("2. Open WebDriverAgent.xcodeproj in Xcode") + print("3. Select WebDriverAgentRunner scheme") + print("4. Select your iOS Simulator as target") + print("5. Build and Run (Cmd+R)") + print("6. It will start on http://localhost:8100") + + if appium_available: + print("✅ Appium server available - can be used as fallback") + print(" Start iOS session to get hierarchy") + else: + print("❌ Appium server not available") + print(" Install: npm install -g appium") + print(" Start: appium server") + + print("\n🎯 Current Status:") + if wda_xml: + print(" Ready to use real iOS hierarchy!") + elif appium_available: + print(" Can use Appium for hierarchy") + else: + print(" No real hierarchy source available - will use fallback") + +if __name__ == "__main__": + main() diff --git a/test_ios_upload.py b/test_ios_upload.py new file mode 100644 index 00000000..d73a92f3 --- /dev/null +++ b/test_ios_upload.py @@ -0,0 +1,23 @@ +#!/usr/bin/env python3 +""" +Test script to start iOS XML upload to server +""" +import sys +import os +import asyncio +sys.path.append(os.path.dirname(__file__)) + +from server.mobile import upload_ios_ui_dump + +async def main(): + print("Starting iOS XML upload to server...") + print("This will continuously capture and upload iOS hierarchy") + print("Press Ctrl+C to stop") + + try: + await upload_ios_ui_dump() + except KeyboardInterrupt: + print("\nStopped iOS XML upload") + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/test_ios_upload_debug.py b/test_ios_upload_debug.py new file mode 100644 index 00000000..fd7b7a35 --- /dev/null +++ b/test_ios_upload_debug.py @@ -0,0 +1,70 @@ +#!/usr/bin/env python3 +import sys +import os +import requests +import hashlib +sys.path.append(os.path.dirname(__file__)) + +from server.mobile import get_ios_devices, capture_ios_ui_dump, IOS_XML_PATH +from Framework.Utilities import ConfigModule, CommonUtil + +def test_upload_once(): + print("Testing iOS XML upload once...") + + try: + # Get device + ios_devices = get_ios_devices() + if not ios_devices: + print("❌ No iOS devices found") + return + + device_udid = ios_devices[0].udid + print(f"✅ Using device: {device_udid}") + + # Capture XML + capture_ios_ui_dump(device_udid) + print("✅ XML captured") + + # Read XML + with open(IOS_XML_PATH, 'r', encoding='utf-8') as xml_file: + xml_content = xml_file.read() + + print(f"✅ XML length: {len(xml_content)} chars") + + # Get config + server_address = ConfigModule.get_config_value("Authentication", "server_address").strip() + api_key = ConfigModule.get_config_value("Authentication", "api-key").strip() + node_id = CommonUtil.MachineInfo().getLocalUser().lower() + + print(f"✅ Server: {server_address}") + print(f"✅ Node ID: {node_id}") + + # Upload + url = server_address + "/node_ai_contents/" + payload = { + "dom_mob": {"dom": xml_content}, + "node_id": node_id + } + + print(f"📤 Uploading to: {url}") + + response = requests.post( + url, + headers={"X-Api-Key": api_key}, + json=payload, + timeout=10 + ) + + print(f"📥 Response status: {response.status_code}") + print(f"📥 Response: {response.text[:200]}...") + + if response.ok: + print("✅ Upload successful!") + else: + print("❌ Upload failed!") + + except Exception as e: + print(f"❌ Error: {e}") + +if __name__ == "__main__": + test_upload_once() From f4572e325a815f02b85e1a11b9237b5446f4bd5a Mon Sep 17 00:00:00 2001 From: mdshakib007 Date: Sun, 14 Dec 2025 17:43:17 +0600 Subject: [PATCH 5/9] added xml and png screenshot of ios in gitignore --- .gitignore | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index a56512c0..8771878d 100644 --- a/.gitignore +++ b/.gitignore @@ -63,4 +63,7 @@ Apps/Windows/inspector.exe Apps/Windows/Element.xml Framework/settings.conf.lock Framework/Built_In_Automation/Desktop/Linux/latest_app.txt -**/linux_screen.png \ No newline at end of file +**/linux_screen.png +**/ios_screen.png +**/ios_ui.xml +**/ui.xml \ No newline at end of file From 9bd1fdfbd128d32b652d16aec68e508373783fcb Mon Sep 17 00:00:00 2001 From: mdshakib007 Date: Sun, 21 Dec 2025 21:23:49 +0600 Subject: [PATCH 6/9] auto simulator launch if not launched to initialize the services. --- server/mobile.py | 50 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/server/mobile.py b/server/mobile.py index 024c1bfe..db70d262 100644 --- a/server/mobile.py +++ b/server/mobile.py @@ -11,6 +11,9 @@ from pydantic import BaseModel from Framework.Utilities import ConfigModule, CommonUtil +import sys + +sys.path.append(os.path.join(os.path.dirname(__file__), '..', 'Framework', 'Built_In_Automation', 'Mobile', 'CrossPlatform', 'Appium')) ADB_PATH = "adb" # Ensure ADB is in PATH UI_XML_PATH = "ui.xml" @@ -131,6 +134,53 @@ def inspect(device_serial: str | None = None): ) +@router.post("/ios/start-services") +def start_ios_services(): + """Start iOS services by calling launch_application function.""" + try: + from Framework.Built_In_Automation.Mobile.CrossPlatform.Appium.BuiltInFunctions import launch_application + from Framework.Built_In_Automation.Shared_Resources import BuiltInFunctionSharedResources as Shared_Resources + + # Get first booted iOS simulator + ios_devices = get_ios_devices() + if not ios_devices: + return {"status": "error", "error": "No iOS simulators available"} + + device_udid = ios_devices[0].udid + device_name = ios_devices[0].name + + # Set up device_info with simulator details (this is what the server normally sends) + device_info = { + "device 1": { + "id": device_udid, + "type": "ios", + "imei": "Simulated", + "model": device_name, + "osver": "17.0" + } + } + + # Set required shared variables + Shared_Resources.Set_Shared_Variables("device_order", None) + Shared_Resources.Set_Shared_Variables("device_info", device_info) + + # Minimal dataset to trigger iOS launch + data_set = [ + ("ios", "element parameter", "com.apple.Preferences"), + ("action", "action", "launch") + ] + + result = launch_application(data_set) + + if result == "passed": + return {"status": "ok", "message": "iOS services started successfully"} + else: + return {"status": "error", "error": "Failed to start iOS services"} + + except Exception as e: + return {"status": "error", "error": str(e)} + + @router.get("/ios/inspect") def inspect_ios(device_udid: str | None = None): """Get iOS simulator screenshot and XML hierarchy.""" From 9336c729a6e19afb991a670af15da4b7ab1cf05a Mon Sep 17 00:00:00 2001 From: mdshakib007 Date: Mon, 22 Dec 2025 13:54:50 +0600 Subject: [PATCH 7/9] auto WebDriverAgent run instead of running any specific app. --- server/mobile.py | 94 ++++++++++++++++++++++-------------------------- 1 file changed, 43 insertions(+), 51 deletions(-) diff --git a/server/mobile.py b/server/mobile.py index db70d262..25ab4506 100644 --- a/server/mobile.py +++ b/server/mobile.py @@ -5,6 +5,7 @@ import json from typing import Literal import asyncio +import socket import requests from fastapi import APIRouter @@ -24,9 +25,17 @@ router = APIRouter(prefix="/mobile", tags=["mobile"]) +def is_wda_running(port: int) -> bool: + """Check if WebDriverAgent is running on given port.""" + try: + response = requests.get(f"http://localhost:{port}/status", timeout=1) + return response.status_code == 200 + except: + return False + + class InspectorResponse(BaseModel): """Response model for the /inspector endpoint.""" - status: Literal["ok", "error"] = "ok" ui_xml: str | None = None screenshot: str | None = None # Base64 encoded image @@ -136,46 +145,34 @@ def inspect(device_serial: str | None = None): @router.post("/ios/start-services") def start_ios_services(): - """Start iOS services by calling launch_application function.""" try: - from Framework.Built_In_Automation.Mobile.CrossPlatform.Appium.BuiltInFunctions import launch_application - from Framework.Built_In_Automation.Shared_Resources import BuiltInFunctionSharedResources as Shared_Resources - - # Get first booted iOS simulator ios_devices = get_ios_devices() if not ios_devices: - return {"status": "error", "error": "No iOS simulators available"} + return {"status": "error", "error": "No booted iOS simulators"} device_udid = ios_devices[0].udid - device_name = ios_devices[0].name - # Set up device_info with simulator details (this is what the server normally sends) - device_info = { - "device 1": { - "id": device_udid, - "type": "ios", - "imei": "Simulated", - "model": device_name, - "osver": "17.0" - } - } + # Check if WDA is already running + wda_port = 8100 + tries = 0 + while tries < 20: + if not is_wda_running(wda_port): + break + wda_port += 2 + tries += 1 - # Set required shared variables - Shared_Resources.Set_Shared_Variables("device_order", None) - Shared_Resources.Set_Shared_Variables("device_info", device_info) + if tries >= 20: + return {"status": "error", "error": "No available WDA ports"} - # Minimal dataset to trigger iOS launch - data_set = [ - ("ios", "element parameter", "com.apple.Preferences"), - ("action", "action", "launch") - ] + result = subprocess.run( + ["xcrun", "simctl", "launch", device_udid, "com.facebook.WebDriverAgentRunner.xctrunner"], + capture_output=True, text=True + ) - result = launch_application(data_set) + if result.returncode != 0: + return {"status": "error", "error": f"Failed to launch WDA: {result.stderr}"} - if result == "passed": - return {"status": "ok", "message": "iOS services started successfully"} - else: - return {"status": "error", "error": "Failed to start iOS services"} + return {"status": "ok", "port": wda_port} except Exception as e: return {"status": "error", "error": str(e)} @@ -185,7 +182,6 @@ def start_ios_services(): def inspect_ios(device_udid: str | None = None): """Get iOS simulator screenshot and XML hierarchy.""" try: - # Get first booted device if none specified if not device_udid: ios_devices = get_ios_devices() if not ios_devices: @@ -203,15 +199,12 @@ def inspect_ios(device_udid: str | None = None): ) device_udid = booted_devices[0].udid - # Capture UI and screenshot (same pattern as Android) capture_ios_ui_dump(device_udid) capture_ios_screenshot(device_udid) - # Read XML file (same pattern as Android) with open(IOS_XML_PATH, 'r', encoding='utf-8') as xml_file: xml_content = xml_file.read() - # Read and encode screenshot with open(IOS_SCREENSHOT_PATH, 'rb') as img_file: screenshot_bytes = img_file.read() screenshot_base64 = base64.b64encode(screenshot_bytes).decode('utf-8') @@ -227,6 +220,7 @@ def inspect_ios(device_udid: str | None = None): error=str(e) ) + @router.get("/dump/driver") def dump_driver(): """Dump the current driver.""" @@ -287,12 +281,9 @@ def capture_screenshot(device_serial: str | None = None): def capture_ios_screenshot(device_udid: str): - """Capture screenshot from iOS simulator.""" try: - # Use absolute path screenshot_path = os.path.abspath(IOS_SCREENSHOT_PATH) - # Remove existing file if it exists if os.path.exists(screenshot_path): os.remove(screenshot_path) @@ -301,7 +292,6 @@ def capture_ios_screenshot(device_udid: str): capture_output=True, text=True, check=True ) - # Verify file was created if not os.path.exists(screenshot_path): raise Exception("Screenshot file was not created") @@ -313,21 +303,24 @@ def capture_ios_screenshot(device_udid: str): def get_real_ios_hierarchy(device_udid: str): - """Try to get real iOS hierarchy using Appium/WebDriverAgent.""" try: import requests - wda_ports = [8100, 8101, 8102] - for port in wda_ports: + wda_port = 8100 + tries = 0 + + while tries < 20: try: - wda_url = f"http://localhost:{port}" + wda_url = f"http://localhost:{wda_port}" # Quick status check status_response = requests.get(f"{wda_url}/status", timeout=1) if status_response.status_code != 200: + wda_port += 2 + tries += 1 continue - # Try existing sessions first + # existing sessions first sessions_response = requests.get(f"{wda_url}/sessions", timeout=1) if sessions_response.status_code == 200: sessions = sessions_response.json() @@ -337,12 +330,14 @@ def get_real_ios_hierarchy(device_udid: str): if source_response.status_code == 200: return source_response.text - # Try direct source + # direct source source_response = requests.get(f"{wda_url}/source", timeout=2) if source_response.status_code == 200: return source_response.text except: + wda_port += 2 + tries += 1 continue except: @@ -352,11 +347,8 @@ def get_real_ios_hierarchy(device_udid: str): def capture_ios_ui_dump(device_udid: str): - """Capture the current UI hierarchy from iOS device (same pattern as Android)""" - # Try WebDriverAgent first (real hierarchy like Android's uiautomator) real_hierarchy = get_real_ios_hierarchy(device_udid) if real_hierarchy: - # Extract XML from JSON wrapper if needed try: import json json_data = json.loads(real_hierarchy) @@ -368,7 +360,7 @@ def capture_ios_ui_dump(device_udid: str): xml_file.write(xml_content) return - # Fallback to Appium driver (same as Android fallback) + # Fallback to Appium driver try: from Framework.Built_In_Automation.Mobile.CrossPlatform.Appium.BuiltInFunctions import appium_driver if appium_driver is not None: @@ -380,7 +372,7 @@ def capture_ios_ui_dump(device_udid: str): pass # No real source available - raise Exception("No iOS UI hierarchy source available. Please start WebDriverAgent (port 8100) or Appium server (port 4723).") + raise Exception("iOS service error. Please reload the iOS inspector page or run a test case.") async def upload_android_ui_dump(): @@ -458,4 +450,4 @@ async def upload_ios_ui_dump(): CommonUtil.ExecLog("", "UI dump uploaded successfully", iLogLevel=1) except Exception as e: CommonUtil.ExecLog("", f"Error uploading iOS UI dump: {str(e)}", iLogLevel=3) - await asyncio.sleep(5) + await asyncio.sleep(5) \ No newline at end of file From 6e38479dac313bb82f48aa335eebacd81df24fc7 Mon Sep 17 00:00:00 2001 From: mdshakib007 Date: Mon, 22 Dec 2025 15:02:06 +0600 Subject: [PATCH 8/9] bundle id is now extracting from the xml file --- server/mobile.py | 18 ++++- test_ios_detection.py | 125 ------------------------------- test_ios_hierarchy.py | 155 --------------------------------------- test_ios_server.py | 32 -------- test_ios_upload.py | 23 ------ test_ios_upload_debug.py | 70 ------------------ 6 files changed, 16 insertions(+), 407 deletions(-) delete mode 100644 test_ios_detection.py delete mode 100644 test_ios_hierarchy.py delete mode 100644 test_ios_server.py delete mode 100644 test_ios_upload.py delete mode 100644 test_ios_upload_debug.py diff --git a/server/mobile.py b/server/mobile.py index 25ab4506..41c62d5d 100644 --- a/server/mobile.py +++ b/server/mobile.py @@ -6,6 +6,7 @@ from typing import Literal import asyncio import socket +import xml.etree.ElementTree as ET import requests from fastapi import APIRouter @@ -39,6 +40,7 @@ class InspectorResponse(BaseModel): status: Literal["ok", "error"] = "ok" ui_xml: str | None = None screenshot: str | None = None # Base64 encoded image + bundle_identifier: str | None = None error: str | None = None @@ -178,6 +180,14 @@ def start_ios_services(): return {"status": "error", "error": str(e)} +def extract_bundle_id_from_xml(xml_content: str) -> str | None: + try: + root = ET.fromstring(xml_content) + return root.get('bundleId') + except Exception: + return None + + @router.get("/ios/inspect") def inspect_ios(device_udid: str | None = None): """Get iOS simulator screenshot and XML hierarchy.""" @@ -204,6 +214,9 @@ def inspect_ios(device_udid: str | None = None): with open(IOS_XML_PATH, 'r', encoding='utf-8') as xml_file: xml_content = xml_file.read() + + # Extract bundle identifier from XML content + bundle_id = extract_bundle_id_from_xml(xml_content) with open(IOS_SCREENSHOT_PATH, 'rb') as img_file: screenshot_bytes = img_file.read() @@ -212,7 +225,8 @@ def inspect_ios(device_udid: str | None = None): return InspectorResponse( status="ok", ui_xml=xml_content, - screenshot=screenshot_base64 + screenshot=screenshot_base64, + bundle_identifier=bundle_id ) except Exception as e: return InspectorResponse( @@ -372,7 +386,7 @@ def capture_ios_ui_dump(device_udid: str): pass # No real source available - raise Exception("iOS service error. Please reload the iOS inspector page or run a test case.") + raise Exception("iOS service error. Make sure simulator is running.") async def upload_android_ui_dump(): diff --git a/test_ios_detection.py b/test_ios_detection.py deleted file mode 100644 index 0e0047f4..00000000 --- a/test_ios_detection.py +++ /dev/null @@ -1,125 +0,0 @@ -import subprocess -import json -import sys - -def test_xcrun_availability(): - """Test if xcrun command is available""" - try: - result = subprocess.run(["xcrun", "--version"], capture_output=True, text=True, check=True) - print("xcrun is available") - print(f"Version: {result.stdout.strip()}") - return True - except (subprocess.CalledProcessError, FileNotFoundError) as e: - print("xcrun is not available") - print(f"Error: {e}") - return False - -def test_simctl_list(): - """Test if simctl list devices works""" - try: - result = subprocess.run( - ["xcrun", "simctl", "list", "devices", "-j"], - capture_output=True, text=True, check=True - ) - print("simctl list devices works") - - devices_data = json.loads(result.stdout) - print(f"Found {len(devices_data.get('devices', {}))} runtime categories") - - total_devices = 0 - available_devices = 0 - - for runtime, devices in devices_data.get("devices", {}).items(): - runtime_available = 0 - for device in devices: - total_devices += 1 - if device.get("isAvailable", False): - available_devices += 1 - runtime_available += 1 - - if runtime_available > 0: - print(f" {runtime}: {runtime_available} available devices") - - print(f"Total devices: {total_devices}") - print(f"Available devices: {available_devices}") - - return available_devices > 0 - - except (subprocess.CalledProcessError, FileNotFoundError, json.JSONDecodeError) as e: - print("simctl list devices failed") - print(f"Error: {e}") - return False - -def test_screenshot_capability(): - """Test if we can take a screenshot from the first available device""" - try: - # Get available devices - result = subprocess.run( - ["xcrun", "simctl", "list", "devices", "-j"], - capture_output=True, text=True, check=True - ) - - devices_data = json.loads(result.stdout) - first_device = None - - for runtime, devices in devices_data.get("devices", {}).items(): - for device in devices: - if device.get("isAvailable", False) and device.get("state") == "Booted": - first_device = device - break - if first_device: - break - - if not first_device: - print("No booted iOS simulators found. Please start an iOS simulator first.") - return False - - print(f"Testing screenshot with device: {first_device['name']} ({first_device['udid']})") - - # Try to take a screenshot - result = subprocess.run( - ["xcrun", "simctl", "io", first_device["udid"], "screenshot", "test_screenshot.png"], - capture_output=True, text=True, check=True - ) - - print("Screenshot capability works") - print("Screenshot saved as test_screenshot.png") - return True - - except (subprocess.CalledProcessError, FileNotFoundError, json.JSONDecodeError) as e: - print("Screenshot test failed") - print(f"Error: {e}") - return False - -def main(): - """Run all tests""" - print("Testing iOS Device Detection and Screenshot Capability") - print("=" * 60) - - tests = [ - ("xcrun availability", test_xcrun_availability), - ("simctl device listing", test_simctl_list), - ("screenshot capability", test_screenshot_capability), - ] - - passed = 0 - total = len(tests) - - for test_name, test_func in tests: - print(f"\n🧪 Testing {test_name}...") - if test_func(): - passed += 1 - print() - - print("=" * 60) - print(f"Tests passed: {passed}/{total}") - - if passed == total: - print("All tests passed! iOS device detection should work.") - return 0 - else: - print("Some tests failed. Please check the requirements.") - return 1 - -if __name__ == "__main__": - sys.exit(main()) diff --git a/test_ios_hierarchy.py b/test_ios_hierarchy.py deleted file mode 100644 index 8146eaf3..00000000 --- a/test_ios_hierarchy.py +++ /dev/null @@ -1,155 +0,0 @@ -#!/usr/bin/env python3 -""" -Test script to check all available iOS hierarchy extraction methods -""" -import requests -import subprocess -import json - -def test_webdriver_agent(): - """Test WebDriverAgent on common ports""" - print("🧪 Testing WebDriverAgent...") - ports = [8100, 8101, 8102] - - for port in ports: - try: - url = f"http://localhost:{port}" - print(f" Trying port {port}...") - - # Check status - response = requests.get(f"{url}/status", timeout=2) - if response.status_code == 200: - print(f" ✅ WebDriverAgent running on port {port}") - - # Check sessions - sessions = requests.get(f"{url}/sessions", timeout=2) - if sessions.status_code == 200: - session_data = sessions.json() - print(f" Sessions: {len(session_data)}") - - if session_data: - session_id = session_data[0]['id'] - source = requests.get(f"{url}/session/{session_id}/source", timeout=3) - if source.status_code == 200: - print(f" ✅ Got XML hierarchy ({len(source.text)} chars)") - return source.text - - # Try direct source - source = requests.get(f"{url}/source", timeout=2) - if source.status_code == 200: - print(f" ✅ Got XML hierarchy via direct source ({len(source.text)} chars)") - return source.text - - except Exception as e: - print(f" ❌ Port {port}: {e}") - - print(" ❌ WebDriverAgent not found") - return None - -def test_appium_server(): - """Test Appium server""" - print("\n🧪 Testing Appium Server...") - try: - response = requests.get("http://localhost:4723/status", timeout=2) - if response.status_code == 200: - print(" ✅ Appium server running on port 4723") - return True - else: - print(" ❌ Appium server not responding") - except Exception as e: - print(f" ❌ Appium server: {e}") - return False - -def test_xcrun_accessibility(): - """Test xcrun accessibility methods""" - print("\n🧪 Testing xcrun accessibility methods...") - - # Get booted device - try: - result = subprocess.run( - ["xcrun", "simctl", "list", "devices", "-j"], - capture_output=True, text=True, check=True - ) - devices_data = json.loads(result.stdout) - booted_device = None - - for runtime, devices in devices_data.get("devices", {}).items(): - for device in devices: - if device.get("state") == "Booted": - booted_device = device["udid"] - print(f" Found booted device: {device['name']} ({booted_device})") - break - if booted_device: - break - - if not booted_device: - print(" ❌ No booted iOS simulator found") - return None - - # Try accessibility inspector - print(" Trying accessibility methods...") - - # Method 1: Try to enable accessibility - try: - subprocess.run([ - "xcrun", "simctl", "spawn", booted_device, - "defaults", "write", "com.apple.Accessibility", "ApplicationAccessibilityEnabled", "-bool", "true" - ], capture_output=True, timeout=5) - print(" ✅ Accessibility enabled") - except: - print(" ⚠️ Could not enable accessibility") - - # Method 2: Try to get accessibility tree (this usually doesn't work without additional tools) - print(" ❌ xcrun doesn't provide direct UI hierarchy access") - - except Exception as e: - print(f" ❌ xcrun accessibility: {e}") - - return None - -def main(): - print("iOS UI Hierarchy Extraction Test") - print("=" * 50) - - # Test all methods - wda_xml = test_webdriver_agent() - appium_available = test_appium_server() - xcrun_result = test_xcrun_accessibility() - - print("\n📋 Summary:") - print("=" * 50) - - if wda_xml: - print("✅ RECOMMENDED: WebDriverAgent is working - use this for real hierarchy") - # Save sample - with open("sample_ios_hierarchy.xml", "w") as f: - f.write(wda_xml) - print(" Sample saved to: sample_ios_hierarchy.xml") - else: - print("❌ WebDriverAgent not available") - print("\n🔧 To set up WebDriverAgent:") - print("1. Clone: git clone https://github.com/appium/WebDriverAgent.git") - print("2. Open WebDriverAgent.xcodeproj in Xcode") - print("3. Select WebDriverAgentRunner scheme") - print("4. Select your iOS Simulator as target") - print("5. Build and Run (Cmd+R)") - print("6. It will start on http://localhost:8100") - - if appium_available: - print("✅ Appium server available - can be used as fallback") - print(" Start iOS session to get hierarchy") - else: - print("❌ Appium server not available") - print(" Install: npm install -g appium") - print(" Start: appium server") - - print("\n🎯 Current Status:") - if wda_xml: - print(" Ready to use real iOS hierarchy!") - elif appium_available: - print(" Can use Appium for hierarchy") - else: - print(" No real hierarchy source available - will use fallback") - -if __name__ == "__main__": - main() diff --git a/test_ios_server.py b/test_ios_server.py deleted file mode 100644 index 4db5fa9f..00000000 --- a/test_ios_server.py +++ /dev/null @@ -1,32 +0,0 @@ -#!/usr/bin/env python3 -""" -Simple test script to verify iOS endpoints work -""" -import sys -import os -sys.path.append(os.path.dirname(__file__)) - -from server.mobile import get_ios_devices, inspect_ios - -def test_ios_endpoints(): - print("Testing iOS endpoints...") - - # Test device listing - print("\n1. Testing get_ios_devices():") - devices = get_ios_devices() - print(f"Found {len(devices)} devices:") - for device in devices: - print(f" - {device.name} ({device.udid})") - - # Test screenshot capture - print("\n2. Testing inspect_ios():") - result = inspect_ios() - print(f"Status: {result.status}") - if result.error: - print(f"Error: {result.error}") - else: - print(f"Screenshot length: {len(result.screenshot) if result.screenshot else 0} characters") - print(f"XML available: {'Yes' if result.ui_xml else 'No'}") - -if __name__ == "__main__": - test_ios_endpoints() diff --git a/test_ios_upload.py b/test_ios_upload.py deleted file mode 100644 index d73a92f3..00000000 --- a/test_ios_upload.py +++ /dev/null @@ -1,23 +0,0 @@ -#!/usr/bin/env python3 -""" -Test script to start iOS XML upload to server -""" -import sys -import os -import asyncio -sys.path.append(os.path.dirname(__file__)) - -from server.mobile import upload_ios_ui_dump - -async def main(): - print("Starting iOS XML upload to server...") - print("This will continuously capture and upload iOS hierarchy") - print("Press Ctrl+C to stop") - - try: - await upload_ios_ui_dump() - except KeyboardInterrupt: - print("\nStopped iOS XML upload") - -if __name__ == "__main__": - asyncio.run(main()) diff --git a/test_ios_upload_debug.py b/test_ios_upload_debug.py deleted file mode 100644 index fd7b7a35..00000000 --- a/test_ios_upload_debug.py +++ /dev/null @@ -1,70 +0,0 @@ -#!/usr/bin/env python3 -import sys -import os -import requests -import hashlib -sys.path.append(os.path.dirname(__file__)) - -from server.mobile import get_ios_devices, capture_ios_ui_dump, IOS_XML_PATH -from Framework.Utilities import ConfigModule, CommonUtil - -def test_upload_once(): - print("Testing iOS XML upload once...") - - try: - # Get device - ios_devices = get_ios_devices() - if not ios_devices: - print("❌ No iOS devices found") - return - - device_udid = ios_devices[0].udid - print(f"✅ Using device: {device_udid}") - - # Capture XML - capture_ios_ui_dump(device_udid) - print("✅ XML captured") - - # Read XML - with open(IOS_XML_PATH, 'r', encoding='utf-8') as xml_file: - xml_content = xml_file.read() - - print(f"✅ XML length: {len(xml_content)} chars") - - # Get config - server_address = ConfigModule.get_config_value("Authentication", "server_address").strip() - api_key = ConfigModule.get_config_value("Authentication", "api-key").strip() - node_id = CommonUtil.MachineInfo().getLocalUser().lower() - - print(f"✅ Server: {server_address}") - print(f"✅ Node ID: {node_id}") - - # Upload - url = server_address + "/node_ai_contents/" - payload = { - "dom_mob": {"dom": xml_content}, - "node_id": node_id - } - - print(f"📤 Uploading to: {url}") - - response = requests.post( - url, - headers={"X-Api-Key": api_key}, - json=payload, - timeout=10 - ) - - print(f"📥 Response status: {response.status_code}") - print(f"📥 Response: {response.text[:200]}...") - - if response.ok: - print("✅ Upload successful!") - else: - print("❌ Upload failed!") - - except Exception as e: - print(f"❌ Error: {e}") - -if __name__ == "__main__": - test_upload_once() From 8c63c17f95005c89ce6105e7afec01aa30ecaaff Mon Sep 17 00:00:00 2001 From: MD Shakib Ahmed Date: Mon, 22 Dec 2025 19:21:15 +0600 Subject: [PATCH 9/9] Potential fix for code scanning alert no. 78: Information exposure through an exception Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> --- server/mobile.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/server/mobile.py b/server/mobile.py index 41c62d5d..543c0496 100644 --- a/server/mobile.py +++ b/server/mobile.py @@ -14,9 +14,9 @@ from Framework.Utilities import ConfigModule, CommonUtil import sys +import logging sys.path.append(os.path.join(os.path.dirname(__file__), '..', 'Framework', 'Built_In_Automation', 'Mobile', 'CrossPlatform', 'Appium')) - ADB_PATH = "adb" # Ensure ADB is in PATH UI_XML_PATH = "ui.xml" SCREENSHOT_PATH = "screen.png" @@ -172,12 +172,13 @@ def start_ios_services(): ) if result.returncode != 0: - return {"status": "error", "error": f"Failed to launch WDA: {result.stderr}"} + return {"status": "error", "error": f"Failed to launch WDA."} return {"status": "ok", "port": wda_port} except Exception as e: - return {"status": "error", "error": str(e)} + logging.exception("Failed to start iOS services") + return {"status": "error", "error": "Failed to start iOS services"} def extract_bundle_id_from_xml(xml_content: str) -> str | None: