diff --git a/Appium Wizard/Appium Wizard.csproj b/Appium Wizard/Appium Wizard.csproj
index 2e0f21e..82449f6 100644
--- a/Appium Wizard/Appium Wizard.csproj
+++ b/Appium Wizard/Appium Wizard.csproj
@@ -209,6 +209,9 @@
PreserveNewest
+
+ PreserveNewest
+
PreserveNewest
diff --git a/Appium Wizard/Appium Wizard.csproj.user b/Appium Wizard/Appium Wizard.csproj.user
index 53c140c..377ee90 100644
--- a/Appium Wizard/Appium Wizard.csproj.user
+++ b/Appium Wizard/Appium Wizard.csproj.user
@@ -19,6 +19,9 @@
Form
+
+ Form
+
Form
diff --git a/Appium Wizard/ExcludeInInstaller/Installer Script.iss b/Appium Wizard/ExcludeInInstaller/Installer Script.iss
index ecb7d18..042459c 100644
--- a/Appium Wizard/ExcludeInInstaller/Installer Script.iss
+++ b/Appium Wizard/ExcludeInInstaller/Installer Script.iss
@@ -2,12 +2,12 @@
; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES!
#define MyAppName "Appium Wizard"
-#define MyAppVersion "8.6.0"
+#define MyAppVersion "8.6.1"
#define MyAppPublisher "Meganathan C"
#define MyAppExeName "Appium Wizard.exe"
[Setup]
-AlwaysRestart = no
+AlwaysRestart = no
AppId={{408E2B01-BCF1-40E3-8F27-F11B32572D03}
AppName={#MyAppName}
UninstallDisplayName = Appium Wizard
diff --git a/Appium Wizard/FilesPath.cs b/Appium Wizard/FilesPath.cs
index bf570af..3d9d33d 100644
--- a/Appium Wizard/FilesPath.cs
+++ b/Appium Wizard/FilesPath.cs
@@ -24,5 +24,7 @@ public static class FilesPath
public static string FFMpegFilePath = Path.GetDirectoryName(AppDomain.CurrentDomain.BaseDirectory) + "\\Resources\\Executables\\FFMpeg\\";
public static string logsFilesPath = Path.GetDirectoryName(AppDomain.CurrentDomain.BaseDirectory) + "\\Resources\\Logs\\";
public static string scrcpy = Path.GetDirectoryName(AppDomain.CurrentDomain.BaseDirectory) + "\\Resources\\Executables\\scrcpy\\scrcpy.exe";
+ public static string ddiDownloaderFilePath = Path.GetDirectoryName(AppDomain.CurrentDomain.BaseDirectory) + "\\Resources\\iOS\\ddi-downloader.exe";
+ public static string devImagesPath = Path.GetDirectoryName(AppDomain.CurrentDomain.BaseDirectory) + "\\Resources\\iOS\\devimages\\";
}
}
diff --git a/Appium Wizard/MainScreen.cs b/Appium Wizard/MainScreen.cs
index a357d3b..b74f461 100644
--- a/Appium Wizard/MainScreen.cs
+++ b/Appium Wizard/MainScreen.cs
@@ -28,6 +28,7 @@ public partial class MainScreen : Form
public static bool useScrcpy;
//public static List UDIDPreInstalledWDA = new List();
public static Dictionary UDIDPreInstalledWDA = new Dictionary();
+ public static HashSet UDIDsUsingRunWDA = new HashSet(); // Devices that should use runwda command instead of launch
private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
private Timer uncheckTimer; private Timer highlightTimer;
public static Dictionary serverNumberWebView = new Dictionary();
diff --git a/Appium Wizard/Object Spy.Designer.cs b/Appium Wizard/Object Spy.Designer.cs
index c839434..6d636d7 100644
--- a/Appium Wizard/Object Spy.Designer.cs
+++ b/Appium Wizard/Object Spy.Designer.cs
@@ -54,6 +54,7 @@ private void InitializeComponent()
addUniqueXpathToFilterToolStripMenuItem1 = new ToolStripMenuItem();
coordLabel = new Label();
helpButton = new Button();
+ downloadXmlButton = new Button();
((System.ComponentModel.ISupportInitialize)pictureBox1).BeginInit();
listViewContextMenuStrip.SuspendLayout();
treeViewContextMenuStrip.SuspendLayout();
@@ -265,6 +266,17 @@ private void InitializeComponent()
helpButton.UseVisualStyleBackColor = true;
helpButton.Click += helpButton_Click;
//
+ // downloadXmlButton
+ //
+ downloadXmlButton.AutoSize = true;
+ downloadXmlButton.Location = new Point(475, 8);
+ downloadXmlButton.Name = "downloadXmlButton";
+ downloadXmlButton.Size = new Size(98, 25);
+ downloadXmlButton.TabIndex = 16;
+ downloadXmlButton.Text = "Download XML";
+ downloadXmlButton.UseVisualStyleBackColor = true;
+ downloadXmlButton.Click += downloadXmlButton_Click;
+ //
// Object_Spy
//
AutoScaleDimensions = new SizeF(7F, 15F);
@@ -273,6 +285,7 @@ private void InitializeComponent()
AutoSizeMode = AutoSizeMode.GrowAndShrink;
BackColor = SystemColors.ControlLightLight;
ClientSize = new Size(1214, 592);
+ Controls.Add(downloadXmlButton);
Controls.Add(helpButton);
Controls.Add(coordLabel);
Controls.Add(filterLabel);
@@ -328,5 +341,6 @@ private void InitializeComponent()
private ToolStripMenuItem addUniqueXpathToFilterToolStripMenuItem1;
private Label coordLabel;
private Button helpButton;
+ private Button downloadXmlButton;
}
}
\ No newline at end of file
diff --git a/Appium Wizard/Object Spy.cs b/Appium Wizard/Object Spy.cs
index fe5bdf2..4138f2e 100644
--- a/Appium Wizard/Object Spy.cs
+++ b/Appium Wizard/Object Spy.cs
@@ -1211,6 +1211,37 @@ private void helpButton_Click(object sender, EventArgs e)
MessageBox.Show(message, "Object Spy - BETA", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
+ private void downloadXmlButton_Click(object sender, EventArgs e)
+ {
+ if (string.IsNullOrEmpty(xmlContent))
+ {
+ MessageBox.Show("No XML content available to save. Please refresh the screen first.", "No Content", MessageBoxButtons.OK, MessageBoxIcon.Warning);
+ return;
+ }
+
+ using (SaveFileDialog saveFileDialog = new SaveFileDialog())
+ {
+ saveFileDialog.Filter = "XML files (*.xml)|*.xml|All files (*.*)|*.*";
+ saveFileDialog.DefaultExt = "xml";
+ saveFileDialog.FileName = $"page_source_{DateTime.Now:yyyyMMdd_HHmmss}.xml";
+ saveFileDialog.Title = "Save XML Hierarchy";
+
+ if (saveFileDialog.ShowDialog() == DialogResult.OK)
+ {
+ try
+ {
+ File.WriteAllText(saveFileDialog.FileName, xmlContent);
+ MessageBox.Show($"XML saved successfully to:\n{saveFileDialog.FileName}", "Success", MessageBoxButtons.OK, MessageBoxIcon.Information);
+ }
+ catch (Exception ex)
+ {
+ Logger.Error(ex, "Error saving XML file");
+ MessageBox.Show($"Error saving XML file:\n{ex.Message}", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
+ }
+ }
+ }
+ }
+
private void Object_Spy_FormClosing(object sender, FormClosingEventArgs e)
{
if (isAndroid)
diff --git a/Appium Wizard/OpenDevice.cs b/Appium Wizard/OpenDevice.cs
index a1dddf2..de90a7b 100644
--- a/Appium Wizard/OpenDevice.cs
+++ b/Appium Wizard/OpenDevice.cs
@@ -339,6 +339,13 @@ await Task.Run(() =>
}
commonProgress.UpdateStepLabel(title, "Mounting developer disk image. Please wait, this may take some time...", 60);
var output = iOSMethods.GetInstance().MountImage(udid);
+
+ // Handle personalized DDI download for iOS 17+/26+
+ if (output.Contains("Downloading personalized DDI"))
+ {
+ commonProgress.UpdateStepLabel(title, "Downloading personalized developer disk image for iOS 17+/26+. This may take a few minutes...", 65);
+ }
+
if (output.Contains("MountImage: failed to get signature from Apple"))
{
commonProgress.Close();
@@ -346,9 +353,36 @@ await Task.Run(() =>
MessageBox.Show("Mounting image failed. Please check your internet connection and try again.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
return;
}
+
+ // Check for identity mismatch error (fallback if personalized DDI also failed)
+ if ((output.Contains("could not find identity") || output.Contains("findIdentity: failed")) && output.Contains("Failed to download personalized DDI"))
+ {
+ commonProgress.Close();
+ isScreenServerStarted = false;
+ MessageBox.Show("Failed to mount developer disk image. The personalized DDI download failed.\nPlease check your internet connection and try again.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
+ return;
+ }
+
if (!iOSMethods.GetInstance().isImageMounted(udid))
{
- iOSMethods.GetInstance().MountImage(udid);
+ Logger.Info("Image not mounted after first attempt, trying again...");
+ commonProgress.UpdateStepLabel(title, "Developer disk image not mounted. Retrying mount...", 62);
+ var retryOutput = iOSMethods.GetInstance().MountImage(udid);
+ Logger.Info("Retry mount output: " + retryOutput);
+
+ // Wait a bit for mount to complete
+ Thread.Sleep(2000);
+
+ // Check again if mounted
+ if (!iOSMethods.GetInstance().isImageMounted(udid))
+ {
+ commonProgress.Close();
+ isScreenServerStarted = false;
+ Logger.Error("Failed to mount developer disk image after retry. Output: " + retryOutput);
+ MessageBox.Show("Failed to mount developer disk image.\n\nPossible causes:\n1. Device was restarted (DDI gets unmounted on restart)\n2. Internet connection issue (for iOS 17+/26+ personalized DDI)\n3. DDI files corrupted\n\nSolution:\n- Ensure device is unlocked\n- Check internet connection\n- Try again\n\nIf issue persists, delete the folder:\n" + FilesPath.devImagesPath + "iOS_DDI\nand try again to re-download the DDI.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
+ return;
+ }
+ Logger.Info("Image mounted successfully after retry");
}
if (deviceVersion >= version17Plus)
{
@@ -374,8 +408,12 @@ await Task.Run(() =>
MessageBox.Show("Please enable Developer Mode in your " + deviceName + " and try again.\nGo to Settings->Privacy & Security->Developer Mode->Turn ON.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
return;
}
- commonProgress.UpdateStepLabel(title, "Starting WebDriverAgent... Please enter passcode in your iPhone, if it asks...\nOnce you see Automation Running, Go to home screen to reduce the retry.", 70);
- WDAsessionId = iOSAsyncMethods.GetInstance().RunWebDriverAgent(commonProgress, udid, proxyPort);
+ commonProgress.UpdateStepLabel(title, "Starting WebDriverAgent, Please enter passcode if it asks...", 70);
+
+ // Use runwda command directly (more stable than launch command)
+ Logger.Info("Using runwda command to start WebDriverAgent");
+ WDAsessionId = iOSAsyncMethods.GetInstance().RunWebDriverAgentWithRunWDA(commonProgress, udid, proxyPort);
+
Logger.Info("WDAsessionId:" + WDAsessionId);
if (WDAsessionId.Equals("Enable Developer Mode"))
{
@@ -402,7 +440,7 @@ await Task.Run(() =>
{
commonProgress.Close();
isScreenServerStarted = false;
- MessageBox.Show("Unable to launch WebDriverAgent on your iPhone. Please enter passcode on your " + deviceName + " when it asks.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
+ MessageBox.Show("Unable to launch WebDriverAgent using runwda command.\n\nPlease:\n1. Enter passcode on your " + deviceName + " when it asks\n2. Ensure Developer Mode is enabled\n3. Unlock your device before opening\n\nNote: If you see WDA running on your device, try going to the home screen and reopening the device.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
return;
}
else if (string.IsNullOrEmpty(WDAsessionId) | WDAsessionId.Equals("unhandled"))
diff --git a/Appium Wizard/Resources/iOS/ddi-downloader.exe b/Appium Wizard/Resources/iOS/ddi-downloader.exe
new file mode 100644
index 0000000..312e013
Binary files /dev/null and b/Appium Wizard/Resources/iOS/ddi-downloader.exe differ
diff --git a/Appium Wizard/ScreenControl.cs b/Appium Wizard/ScreenControl.cs
index 65df387..f05e150 100644
--- a/Appium Wizard/ScreenControl.cs
+++ b/Appium Wizard/ScreenControl.cs
@@ -731,6 +731,20 @@ private void ScreenControl_FormClosing(object sender, FormClosingEventArgs e)
}
udidScreenControl.Remove(udid);
webview2.Remove(udid);
+
+ // Stop runwda process if running for this device
+ if (OSType.Equals("iOS"))
+ {
+ try
+ {
+ iOSAsyncMethods.GetInstance().StopRunWDAProcess(udid);
+ }
+ catch (Exception ex)
+ {
+ Logger.Error(ex, "Failed to stop RunWDA process during form closing");
+ }
+ }
+
//Hide();
//try
//{
diff --git a/Appium Wizard/USBWatcher.cs b/Appium Wizard/USBWatcher.cs
index f668764..1e60ef4 100644
--- a/Appium Wizard/USBWatcher.cs
+++ b/Appium Wizard/USBWatcher.cs
@@ -258,12 +258,13 @@ private void UsbDeviceDisconnected(object sender, EventArrivedEventArgs e)
Database.UpdateDataInDevicesTable(udid, "Connection", "");
}
}
- if (ScreenControl.webview2.ContainsKey(udid))
- {
- var control = ScreenControl.udidScreenControl[udid];
- control.LoadDeviceDisconnected(udid);
- }
- else if (ScreenControl.udidScreenControl.ContainsKey(udid))
+ //if (ScreenControl.webview2.ContainsKey(udid))
+ //{
+ // var control = ScreenControl.udidScreenControl[udid];
+ // control.LoadDeviceDisconnected(udid);
+ //}
+ //else if (ScreenControl.udidScreenControl.ContainsKey(udid))
+ if (ScreenControl.udidScreenControl.ContainsKey(udid))
{
var control = ScreenControl.udidScreenControl[udid];
control.Close();
diff --git a/Appium Wizard/VersionInfo.cs b/Appium Wizard/VersionInfo.cs
index c4ef12e..c3b1140 100644
--- a/Appium Wizard/VersionInfo.cs
+++ b/Appium Wizard/VersionInfo.cs
@@ -2,7 +2,7 @@
{
public static class VersionInfo
{
- public const string VersionNumber = "8.6.0";
- public const string ReleaseNotes = "Server settings, screen control, download wda, main screen close update";
+ public const string VersionNumber = "8.6.1";
+ public const string ReleaseNotes = "Some iPhones not working fix";
}
}
diff --git a/Appium Wizard/iOSMethods.cs b/Appium Wizard/iOSMethods.cs
index 603d157..ed0df73 100644
--- a/Appium Wizard/iOSMethods.cs
+++ b/Appium Wizard/iOSMethods.cs
@@ -919,7 +919,83 @@ public string MountImage(string udid)
{
if (isGo)
{
- return ExecuteCommand("image auto", udid);
+ // First, check if personalized DDI already exists (for iOS 17+/26+)
+ string personalizedDdiPath = FilesPath.devImagesPath + "iOS_DDI\\Restore";
+ bool hasPersonalizedDDI = Directory.Exists(personalizedDdiPath);
+
+ if (hasPersonalizedDDI)
+ {
+ // Personalized DDI exists, check if it's valid
+ var dmgFiles = Directory.GetFiles(personalizedDdiPath, "*.dmg");
+ var plistFiles = Directory.GetFiles(personalizedDdiPath, "BuildManifest.plist");
+
+ if (dmgFiles.Length > 0 && plistFiles.Length > 0)
+ {
+ Logger.Info("Found existing personalized DDI at: " + personalizedDdiPath + ". Attempting to mount directly.");
+
+ // Try mounting the personalized DDI directly
+ string output = ExecuteCommand("image mount --path=\"" + personalizedDdiPath + "\"", udid);
+
+ if (output.Contains("\"level\":\"info\",\"msg\":\"ok\"") || output.Contains("successfully mounted"))
+ {
+ Logger.Info("Personalized DDI mounted successfully");
+ return output;
+ }
+ else if (output.Contains("DeviceLocked"))
+ {
+ Logger.Info("Device is locked. Please unlock and retry.");
+ return output;
+ }
+ else
+ {
+ Logger.Warn("Failed to mount existing personalized DDI. Output: " + output);
+ // Continue to try image auto as fallback
+ }
+ }
+ else
+ {
+ Logger.Warn("Personalized DDI folder exists but files are incomplete. Will try to re-download.");
+ }
+ }
+
+ // Try standard image auto command
+ Logger.Info("Trying standard image auto command");
+ string autoOutput = ExecuteCommand("image auto", udid);
+
+ // Check if it succeeded
+ if (autoOutput.Contains("\"level\":\"info\",\"msg\":\"ok\"") || autoOutput.Contains("successfully mounted"))
+ {
+ Logger.Info("Standard DDI mounted successfully");
+ return autoOutput;
+ }
+
+ // Check if mounting failed due to identity mismatch (iOS 17+ / iOS 26.3+ personalized DDI issue)
+ if (autoOutput.Contains("could not find identity") || autoOutput.Contains("findIdentity: failed"))
+ {
+ Logger.Info("Standard DDI mounting failed with identity mismatch. Attempting to download personalized DDI for iOS 17+/26+...");
+
+ // Download personalized DDI using ddi-downloader.exe
+ personalizedDdiPath = DownloadPersonalizedDDI();
+
+ if (!string.IsNullOrEmpty(personalizedDdiPath) && Directory.Exists(personalizedDdiPath))
+ {
+ Logger.Info("Personalized DDI downloaded. Attempting to mount from: " + personalizedDdiPath);
+
+ // Mount the personalized DDI
+ autoOutput = ExecuteCommand("image mount --path=\"" + personalizedDdiPath + "\"", udid);
+
+ if (autoOutput.Contains("DeviceLocked"))
+ {
+ Logger.Info("Device is locked. Please unlock and retry.");
+ }
+ }
+ else
+ {
+ Logger.Error("Failed to download personalized DDI");
+ }
+ }
+
+ return autoOutput;
}
else
{
@@ -932,6 +1008,111 @@ public string MountImage(string udid)
}
}
+ private string DownloadPersonalizedDDI()
+ {
+ try
+ {
+ if (!File.Exists(FilesPath.ddiDownloaderFilePath))
+ {
+ Logger.Error("ddi-downloader.exe not found at: " + FilesPath.ddiDownloaderFilePath);
+ return null;
+ }
+
+ // Ensure devimages directory exists
+ if (!Directory.Exists(FilesPath.devImagesPath))
+ {
+ Directory.CreateDirectory(FilesPath.devImagesPath);
+ }
+
+ string outputPath = FilesPath.devImagesPath + "iOS_DDI";
+
+ // If DDI already exists, check if it's valid before re-downloading
+ string restorePath = Path.Combine(outputPath, "Restore");
+ if (Directory.Exists(restorePath))
+ {
+ // Check if required files exist
+ var dmgFiles = Directory.GetFiles(restorePath, "*.dmg");
+ var plistFiles = Directory.GetFiles(restorePath, "BuildManifest.plist");
+
+ if (dmgFiles.Length > 0 && plistFiles.Length > 0)
+ {
+ Logger.Info("Personalized DDI already exists at: " + restorePath);
+ return restorePath;
+ }
+ else
+ {
+ Logger.Info("Existing DDI is incomplete, re-downloading...");
+ Directory.Delete(outputPath, true);
+ }
+ }
+
+ Logger.Info("Downloading personalized DDI to: " + outputPath);
+
+ ProcessStartInfo startInfo = new ProcessStartInfo
+ {
+ FileName = FilesPath.ddiDownloaderFilePath,
+ Arguments = "--output \"" + outputPath + "\"",
+ UseShellExecute = false,
+ CreateNoWindow = true,
+ RedirectStandardOutput = true,
+ RedirectStandardError = true,
+ WorkingDirectory = FilesPath.iOSFilesPath
+ };
+
+ Process process = new Process();
+ process.StartInfo = startInfo;
+ process.Start();
+
+ string output = process.StandardOutput.ReadToEnd();
+ string error = process.StandardError.ReadToEnd();
+
+ process.WaitForExit(120000); // Wait up to 2 minutes for download
+
+ Logger.Info("DDI Downloader output: " + output);
+
+ if (!string.IsNullOrEmpty(error))
+ {
+ Logger.Error("DDI Downloader error: " + error);
+ }
+
+ if (process.ExitCode != 0)
+ {
+ Logger.Error("DDI Downloader exited with code: " + process.ExitCode);
+ return null;
+ }
+
+ // The ddi-downloader creates the structure compatible with go-ios
+ // It should create: iOS_DDI/Restore/ with the necessary files
+ if (Directory.Exists(restorePath))
+ {
+ // Verify the required files exist
+ var dmgFiles = Directory.GetFiles(restorePath, "*.dmg");
+ var plistFiles = Directory.GetFiles(restorePath, "BuildManifest.plist");
+
+ if (dmgFiles.Length > 0 && plistFiles.Length > 0)
+ {
+ Logger.Info("Successfully downloaded personalized DDI with " + dmgFiles.Length + " DMG file(s)");
+ return restorePath;
+ }
+ else
+ {
+ Logger.Error("DDI download completed but required files (DMG/BuildManifest) not found");
+ return null;
+ }
+ }
+ else
+ {
+ Logger.Error("DDI download completed but Restore folder not found");
+ return null;
+ }
+ }
+ catch (Exception ex)
+ {
+ Logger.Error(ex, "Failed to download personalized DDI");
+ return null;
+ }
+ }
+
public bool isImageMounted(string udid)
{
var output = ExecuteCommand("image list", udid);
@@ -1403,6 +1584,7 @@ public class iOSAsyncMethods
private Process pyAsyncProcess;
private StringBuilder outputBuffer;
public static Dictionary? PortProcessId;
+ public static Dictionary? RunWDAProcesses; // Tracks runwda processes by UDID
public enum iOSExecutable { go, py }
public static bool isGo;
public static bool is17Plus;
@@ -1411,6 +1593,7 @@ public iOSAsyncMethods()
{
InitializeProcess();
PortProcessId = new Dictionary();
+ RunWDAProcesses = new Dictionary();
}
public static iOSAsyncMethods GetInstance()
@@ -1782,7 +1965,7 @@ public string RunWebDriverAgentQuick(CommonProgress commonProgress, string udid,
{
break;
}
- commonProgress.UpdateStepLabel("Restarting WebDriverAgentRunner...\nRetry " + count + "/5.");
+ commonProgress.UpdateStepLabel("Restarting WebDriverAgentRunner, Please enter passcode if it asks...\nRetry " + count + "/5.");
iOSMethods.GetInstance().KillWDA(udid);
iOSMethods.GetInstance().RunWebDriverAgentQuick(udid);
commonProgress.UpdateStepLabel("Please enter Passcode on your iPhone if it asks...\nOnce you see Automation Running, Go to home screen to reduce the retry.\nRetry " + count + "/5.");
@@ -1806,6 +1989,190 @@ public string RunWebDriverAgentQuick(CommonProgress commonProgress, string udid,
}
}
+ ///
+ /// Runs WebDriverAgent using the 'runwda' command which keeps the process running continuously.
+ /// This is more stable than 'launch' command for devices where WDA closes after a few seconds.
+ /// The process is tracked and can be killed when the device is closed.
+ ///
+ public string RunWebDriverAgentWithRunWDA(CommonProgress commonProgress, string udid, int port)
+ {
+ try
+ {
+ Logger.Info("RunWebDriverAgentWithRunWDA called for UDID: " + udid);
+
+ // Check if WDA is already running
+ string sessionId = iOSMethods.GetInstance().IsWDARunning(port);
+ if (!sessionId.Equals("nosession"))
+ {
+ Logger.Info("WDA already running with session: " + sessionId);
+ return sessionId;
+ }
+
+ // Check if runwda process is already running for this device
+ if (RunWDAProcesses != null && RunWDAProcesses.ContainsKey(udid))
+ {
+ Process existingProcess = RunWDAProcesses[udid];
+ if (existingProcess != null && !existingProcess.HasExited)
+ {
+ Logger.Info("RunWDA process already running for UDID: " + udid);
+ // Wait for WDA to become available
+ sessionId = WaitForWDASession(port, 30);
+ return sessionId;
+ }
+ else
+ {
+ // Process has exited, remove it
+ RunWDAProcesses.Remove(udid);
+ }
+ }
+
+ // Check if WDA is installed
+ bool isInstalled = iOSMethods.GetInstance().iSWDAInstalled(udid);
+ if (!isInstalled)
+ {
+ Logger.Info("WDA not installed for UDID: " + udid);
+ return "WDA Not Installed";
+ }
+
+ // Create and start the runwda process
+ ProcessStartInfo startInfo = new ProcessStartInfo
+ {
+ FileName = FilesPath.iOSServerFilePath,
+ Arguments = "runwda --udid=" + udid + " --env USE_PORT=" + "8100",
+ UseShellExecute = false,
+ CreateNoWindow = true,
+ RedirectStandardOutput = true,
+ RedirectStandardError = true
+ };
+
+ Process runWDAProcess = new Process();
+ runWDAProcess.StartInfo = startInfo;
+
+ // Set up output handlers to monitor WDA status
+ runWDAProcess.OutputDataReceived += (sender, e) =>
+ {
+ if (!string.IsNullOrEmpty(e.Data))
+ {
+ Logger.Debug("RunWDA Output: " + e.Data);
+ if (e.Data.Contains("authorized\":true") || e.Data.Contains("ServerURLHere"))
+ {
+ Logger.Info("WDA authorization detected");
+ }
+ }
+ };
+
+ runWDAProcess.ErrorDataReceived += (sender, e) =>
+ {
+ if (!string.IsNullOrEmpty(e.Data))
+ {
+ Logger.Debug("RunWDA Error: " + e.Data);
+ if (e.Data.Contains("Could not start service"))
+ {
+ Logger.Error("Developer mode disabled");
+ }
+ }
+ };
+
+ Logger.Info("Starting runwda process for UDID: " + udid);
+ runWDAProcess.Start();
+ runWDAProcess.BeginOutputReadLine();
+ runWDAProcess.BeginErrorReadLine();
+
+ // Store the process reference
+ if (RunWDAProcesses == null)
+ {
+ RunWDAProcesses = new Dictionary();
+ }
+ RunWDAProcesses[udid] = runWDAProcess;
+
+ Logger.Info("RunWDA process started with PID: " + runWDAProcess.Id);
+
+ // Wait for WDA to start and create session
+ //commonProgress.UpdateStepLabel("Waiting for WebDriverAgent to start using runwda command...", 75);
+ sessionId = WaitForWDASession(port, 30);
+
+ if (sessionId.Equals("nosession"))
+ {
+ Logger.Warn("Failed to get WDA session after starting runwda");
+ // Check if process is still running
+ if (runWDAProcess.HasExited)
+ {
+ Logger.Error("RunWDA process exited unexpectedly with code: " + runWDAProcess.ExitCode);
+ RunWDAProcesses.Remove(udid);
+ }
+ }
+ else
+ {
+ Logger.Info("Successfully got WDA session: " + sessionId);
+ iOSAPIMethods.GoToHome(port);
+ }
+
+ return sessionId;
+ }
+ catch (Exception ex)
+ {
+ Logger.Error(ex, "Exception in RunWebDriverAgentWithRunWDA");
+ return "unhandled";
+ }
+ }
+
+ ///
+ /// Waits for WDA to become available and returns the session ID
+ ///
+ private string WaitForWDASession(int port, int timeoutSeconds)
+ {
+ string sessionId = "nosession";
+ int attempts = timeoutSeconds / 2; // Check every 2 seconds
+ int count = 0;
+
+ while (sessionId.Equals("nosession") && count < attempts)
+ {
+ Thread.Sleep(2000);
+ sessionId = iOSAPIMethods.CreateWDASession(port);
+ if (!sessionId.Equals("nosession"))
+ {
+ Logger.Info("WDA session created: " + sessionId);
+ break;
+ }
+ count++;
+ Logger.Debug("Waiting for WDA session... Attempt " + (count + 1) + "/" + attempts);
+ }
+
+ if (sessionId.Equals("nosession"))
+ {
+ // Try one more time to check if WDA is already running
+ sessionId = iOSMethods.GetInstance().IsWDARunning(port);
+ }
+
+ return sessionId;
+ }
+
+ ///
+ /// Stops the runwda process for a specific device
+ ///
+ public void StopRunWDAProcess(string udid)
+ {
+ try
+ {
+ if (RunWDAProcesses != null && RunWDAProcesses.ContainsKey(udid))
+ {
+ Process process = RunWDAProcesses[udid];
+ if (process != null && !process.HasExited)
+ {
+ Logger.Info("Stopping RunWDA process for UDID: " + udid);
+ process.Kill();
+ process.WaitForExit(5000);
+ Logger.Info("RunWDA process stopped");
+ }
+ RunWDAProcesses.Remove(udid);
+ }
+ }
+ catch (Exception ex)
+ {
+ Logger.Error(ex, "Failed to stop RunWDA process for UDID: " + udid);
+ }
+ }
+
Process tunnelProcess;
public bool CreateTunnelPy()
{