Skip to content

Commit 5e9c1a3

Browse files
committed
Fix exception handling during session creation
Fix that exceptions during session creation could leave the app process still running.
1 parent 814e059 commit 5e9c1a3

2 files changed

Lines changed: 71 additions & 53 deletions

File tree

src/FlaUI.WebDriver/Controllers/SessionController.cs

Lines changed: 68 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using FlaUI.Core;
12
using FlaUI.WebDriver.Models;
23
using Microsoft.AspNetCore.Mvc;
34
using System.Diagnostics;
@@ -29,8 +30,6 @@ public async Task<ActionResult> CreateNewSession([FromBody] CreateSessionRequest
2930
.Where(capabilities => TryMatchCapabilities(capabilities, out matchedCapabilities, out _))
3031
.Select(capabillities => matchedCapabilities!);
3132

32-
Core.Application? app;
33-
var isAppOwnedBySession = false;
3433
var capabilities = matchingCapabilities.FirstOrDefault();
3534
if (capabilities == null)
3635
{
@@ -43,69 +42,87 @@ public async Task<ActionResult> CreateNewSession([FromBody] CreateSessionRequest
4342
Message = string.Join("; ", mismatchIndications)
4443
});
4544
}
45+
46+
var timeoutsConfiguration = new TimeoutsConfiguration();
47+
if (capabilities.TryGetCapability<TimeoutsConfiguration>("timeouts", out var timeoutsConfigurationFromCapabilities))
48+
{
49+
timeoutsConfiguration = timeoutsConfigurationFromCapabilities!;
50+
}
51+
var newCommandTimeout = TimeSpan.FromSeconds(60);
52+
if (capabilities.TryGetNumberCapability("appium:newCommandTimeout", out var newCommandTimeoutFromCapabilities))
53+
{
54+
newCommandTimeout = TimeSpan.FromSeconds(newCommandTimeoutFromCapabilities);
55+
}
56+
57+
Application? app = GetApp(capabilities, out bool isAppOwnedBySession);
58+
Session session;
59+
try
60+
{
61+
session = new Session(app, isAppOwnedBySession, timeoutsConfiguration, newCommandTimeout);
62+
}
63+
catch (Exception)
64+
{
65+
app?.Dispose();
66+
throw;
67+
}
68+
69+
_sessionRepository.Add(session);
70+
_logger.LogInformation("Created session with ID {SessionId} and capabilities {Capabilities}", session.SessionId, capabilities);
71+
return await Task.FromResult(WebDriverResult.Success(new CreateSessionResponse()
72+
{
73+
SessionId = session.SessionId,
74+
Capabilities = capabilities.Capabilities
75+
}));
76+
}
77+
78+
private static Application? GetApp(MergedCapabilities capabilities, out bool isAppOwnedBySession)
79+
{
4680
if (capabilities.TryGetStringCapability("appium:app", out var appPath))
4781
{
4882
if (appPath == "Root")
4983
{
50-
app = null;
84+
isAppOwnedBySession = false;
85+
return null;
5186
}
52-
else
87+
88+
capabilities.TryGetStringCapability("appium:appArguments", out var appArguments);
89+
try
5390
{
54-
capabilities.TryGetStringCapability("appium:appArguments", out var appArguments);
55-
try
91+
if (appPath.EndsWith("!App"))
5692
{
57-
if (appPath.EndsWith("!App"))
58-
{
59-
app = Core.Application.LaunchStoreApp(appPath, appArguments);
60-
}
61-
else
62-
{
63-
var processStartInfo = new ProcessStartInfo(appPath, appArguments ?? "");
64-
if(capabilities.TryGetStringCapability("appium:appWorkingDir", out var appWorkingDir))
65-
{
66-
processStartInfo.WorkingDirectory = appWorkingDir;
67-
}
68-
app = Core.Application.Launch(processStartInfo);
69-
}
93+
isAppOwnedBySession = true;
94+
return Application.LaunchStoreApp(appPath, appArguments);
7095
}
71-
catch(Exception e)
96+
97+
var processStartInfo = new ProcessStartInfo(appPath, appArguments ?? "");
98+
if (capabilities.TryGetStringCapability("appium:appWorkingDir", out var appWorkingDir))
7299
{
73-
throw WebDriverResponseException.InvalidArgument($"Starting app '{appPath}' with arguments '{appArguments}' threw an exception: {e.Message}");
100+
processStartInfo.WorkingDirectory = appWorkingDir;
74101
}
102+
isAppOwnedBySession = true;
103+
return Application.Launch(processStartInfo);
104+
}
105+
catch (Exception e)
106+
{
107+
throw WebDriverResponseException.InvalidArgument($"Starting app '{appPath}' with arguments '{appArguments}' threw an exception: {e.Message}");
75108
}
76-
77-
isAppOwnedBySession = true;
78109
}
79-
else if (capabilities.TryGetStringCapability("appium:appTopLevelWindow", out var appTopLevelWindowString))
110+
111+
if (capabilities.TryGetStringCapability("appium:appTopLevelWindow", out var appTopLevelWindowString))
80112
{
113+
isAppOwnedBySession = false;
81114
Process process = GetProcessByMainWindowHandle(appTopLevelWindowString);
82-
app = Core.Application.Attach(process);
115+
return Application.Attach(process);
83116
}
84-
else if (capabilities.TryGetStringCapability("appium:appTopLevelWindowTitleMatch", out var appTopLevelWindowTitleMatch))
117+
118+
if (capabilities.TryGetStringCapability("appium:appTopLevelWindowTitleMatch", out var appTopLevelWindowTitleMatch))
85119
{
120+
isAppOwnedBySession = false;
86121
Process? process = GetProcessByMainWindowTitle(appTopLevelWindowTitleMatch);
87-
app = Core.Application.Attach(process);
88-
}
89-
else
90-
{
91-
throw WebDriverResponseException.InvalidArgument("One of appium:app, appium:appTopLevelWindow or appium:appTopLevelWindowTitleMatch must be passed as a capability");
92-
}
93-
var session = new Session(app, isAppOwnedBySession);
94-
if(capabilities.TryGetNumberCapability("appium:newCommandTimeout", out var newCommandTimeout))
95-
{
96-
session.NewCommandTimeout = TimeSpan.FromSeconds(newCommandTimeout);
122+
return Application.Attach(process);
97123
}
98-
if (capabilities.TryGetCapability<TimeoutsConfiguration>("timeouts", out var timeoutsConfiguration))
99-
{
100-
session.TimeoutsConfiguration = timeoutsConfiguration!;
101-
}
102-
_sessionRepository.Add(session);
103-
_logger.LogInformation("Created session with ID {SessionId} and capabilities {Capabilities}", session.SessionId, capabilities);
104-
return await Task.FromResult(WebDriverResult.Success(new CreateSessionResponse()
105-
{
106-
SessionId = session.SessionId,
107-
Capabilities = capabilities.Capabilities
108-
}));
124+
125+
throw WebDriverResponseException.InvalidArgument("One of appium:app, appium:appTopLevelWindow or appium:appTopLevelWindowTitleMatch must be passed as a capability");
109126
}
110127

111128
private string? GetMismatchIndication(MergedCapabilities capabilities)
@@ -163,7 +180,7 @@ private bool TryMatchCapabilities(MergedCapabilities capabilities, [MaybeNullWhe
163180
matchedCapabilities.Copy("appium:appTopLevelWindow", capabilities);
164181
}
165182
else if (capabilities.Contains("appium:appTopLevelWindowTitleMatch"))
166-
{
183+
{
167184
matchedCapabilities.Copy("appium:appTopLevelWindowTitleMatch", capabilities);
168185
}
169186
else
@@ -199,8 +216,8 @@ private static Process GetProcessByMainWindowTitle(string appTopLevelWindowTitle
199216
try
200217
{
201218
appMainWindowTitleRegex = new Regex(appTopLevelWindowTitleMatch);
202-
}
203-
catch(ArgumentException e)
219+
}
220+
catch (ArgumentException e)
204221
{
205222
throw WebDriverResponseException.InvalidArgument($"Capability appium:appTopLevelWindowTitleMatch '{appTopLevelWindowTitleMatch}' is not a valid regular expression: {e.Message}");
206223
}
@@ -209,7 +226,7 @@ private static Process GetProcessByMainWindowTitle(string appTopLevelWindowTitle
209226
{
210227
throw WebDriverResponseException.InvalidArgument($"Process with main window title matching '{appTopLevelWindowTitleMatch}' could not be found");
211228
}
212-
else if (processes.Length > 1)
229+
if (processes.Length > 1)
213230
{
214231
throw WebDriverResponseException.InvalidArgument($"Found multiple ({processes.Length}) processes with main window title matching '{appTopLevelWindowTitleMatch}'");
215232
}

src/FlaUI.WebDriver/Session.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,15 @@ namespace FlaUI.WebDriver
88
{
99
public class Session : IDisposable
1010
{
11-
public Session(Application? app, bool isAppOwnedBySession)
11+
public Session(Application? app, bool isAppOwnedBySession, TimeoutsConfiguration timeoutsConfiguration, TimeSpan newCommandTimeout)
1212
{
1313
App = app;
1414
SessionId = Guid.NewGuid().ToString();
1515
Automation = new UIA3Automation();
1616
InputState = new InputState();
17-
TimeoutsConfiguration = new TimeoutsConfiguration();
17+
TimeoutsConfiguration = timeoutsConfiguration;
1818
IsAppOwnedBySession = isAppOwnedBySession;
19+
NewCommandTimeout = newCommandTimeout;
1920

2021
if (app != null)
2122
{

0 commit comments

Comments
 (0)