Skip to content

Commit ad6a4d4

Browse files
committed
Add new session timeout feature
Cleanup unused sessions after the `appium:newCommandTimeout` interval (default 60 seconds).
1 parent 651e548 commit ad6a4d4

21 files changed

Lines changed: 199 additions & 31 deletions

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ The following capabilities are supported:
2828
| appium:appArguments | Application arguments string, for example `/?`. |
2929
| appium:appTopLevelWindow | The hexadecimal handle of an existing application top level window to attach to, for example `0x12345` (should be of string type). Either this capability, `appTopLevelWindowTitleMatch` or `app` must be provided on session startup. | `0xC0B46` |
3030
| appium:appTopLevelWindowTitleMatch | The title of an existing application top level window to attach to, for example `My App Window Title` (should be of string type). Either this capability, `appTopLevelWindow` or `app` must be provided on session startup. | `My App Window Title` or `My App Window Title - .*` |
31+
| appium:newCommandTimeout | The number of seconds the to wait for clients to send commands before deciding that the client has gone away and the session should shut down. Default one minute (60). | `120` |
3132

3233
## Getting Started
3334

src/FlaUI.WebDriver.UITests/SessionTests.cs

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,5 +135,56 @@ public void GetTitle_Default_IsSupported()
135135

136136
Assert.That(title, Is.EqualTo("FlaUI WPF Test App"));
137137
}
138+
139+
[Test, Explicit("Takes too long (one minute)")]
140+
public void NewCommandTimeout_DefaultValue_OneMinute()
141+
{
142+
var driverOptions = FlaUIDriverOptions.TestApp();
143+
using var driver = new RemoteWebDriver(WebDriverFixture.WebDriverUrl, driverOptions);
144+
145+
System.Threading.Thread.Sleep(TimeSpan.FromSeconds(60) + WebDriverFixture.SessionCleanupInterval*2);
146+
147+
Assert.That(() => driver.Title, Throws.TypeOf<WebDriverException>().With.Message.Matches("No active session with ID '.*'"));
148+
}
149+
150+
[Test]
151+
public void NewCommandTimeout_Expired_EndsSession()
152+
{
153+
var driverOptions = FlaUIDriverOptions.TestApp();
154+
driverOptions.AddAdditionalOption("appium:newCommandTimeout", 1);
155+
using var driver = new RemoteWebDriver(WebDriverFixture.WebDriverUrl, driverOptions);
156+
157+
System.Threading.Thread.Sleep(TimeSpan.FromSeconds(1) + WebDriverFixture.SessionCleanupInterval * 2);
158+
159+
Assert.That(() => driver.Title, Throws.TypeOf<WebDriverException>().With.Message.Matches("No active session with ID '.*'"));
160+
}
161+
162+
[Test]
163+
public void NewCommandTimeout_ReceivedCommandsBeforeExpiry_DoesNotEndSession()
164+
{
165+
var driverOptions = FlaUIDriverOptions.TestApp();
166+
driverOptions.AddAdditionalOption("appium:newCommandTimeout", WebDriverFixture.SessionCleanupInterval.TotalSeconds * 4);
167+
using var driver = new RemoteWebDriver(WebDriverFixture.WebDriverUrl, driverOptions);
168+
169+
System.Threading.Thread.Sleep(WebDriverFixture.SessionCleanupInterval * 2);
170+
_ = driver.Title;
171+
System.Threading.Thread.Sleep(WebDriverFixture.SessionCleanupInterval * 2);
172+
_ = driver.Title;
173+
System.Threading.Thread.Sleep(WebDriverFixture.SessionCleanupInterval * 2);
174+
175+
Assert.That(() => driver.Title, Throws.Nothing);
176+
}
177+
178+
[Test]
179+
public void NewCommandTimeout_NotExpired_DoesNotEndSession()
180+
{
181+
var driverOptions = FlaUIDriverOptions.TestApp();
182+
driverOptions.AddAdditionalOption("appium:newCommandTimeout", 240);
183+
using var driver = new RemoteWebDriver(WebDriverFixture.WebDriverUrl, driverOptions);
184+
185+
System.Threading.Thread.Sleep(WebDriverFixture.SessionCleanupInterval * 2);
186+
187+
Assert.That(() => driver.Title, Throws.Nothing);
188+
}
138189
}
139190
}

src/FlaUI.WebDriver.UITests/WebDriverFixture.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ namespace FlaUI.WebDriver.UITests
99
[SetUpFixture]
1010
public class WebDriverFixture
1111
{
12-
public static readonly Uri WebDriverUrl = new Uri("http://localhost:9723/");
12+
public static readonly Uri WebDriverUrl = new Uri("http://localhost:4723/");
13+
public static readonly TimeSpan SessionCleanupInterval = TimeSpan.FromSeconds(1);
1314

1415
private Process _webDriverProcess;
1516

@@ -20,7 +21,7 @@ public void Setup()
2021
Directory.SetCurrentDirectory(assemblyDir);
2122

2223
string webDriverPath = Path.Combine(Directory.GetCurrentDirectory(), "FlaUI.WebDriver.exe");
23-
var webDriverArguments = $"--urls={WebDriverUrl}";
24+
var webDriverArguments = $"--urls={WebDriverUrl} --SessionCleanup:SchedulingIntervalSeconds={SessionCleanupInterval.TotalSeconds}";
2425
var webDriverProcessStartInfo = new ProcessStartInfo(webDriverPath, webDriverArguments)
2526
{
2627
RedirectStandardError = true,

src/FlaUI.WebDriver/Controllers/ActionsController.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -333,6 +333,7 @@ private Session GetSession(string sessionId)
333333
{
334334
throw WebDriverResponseException.SessionNotFound(sessionId);
335335
}
336+
session.SetLastCommandTimeToNow();
336337
return session;
337338
}
338339
}

src/FlaUI.WebDriver/Controllers/ElementController.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,7 @@ private Session GetSession(string sessionId)
219219
{
220220
throw WebDriverResponseException.SessionNotFound(sessionId);
221221
}
222+
session.SetLastCommandTimeToNow();
222223
return session;
223224
}
224225
}

src/FlaUI.WebDriver/Controllers/ExecuteController.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ private Session GetSession(string sessionId)
7878
{
7979
throw WebDriverResponseException.SessionNotFound(sessionId);
8080
}
81+
session.SetLastCommandTimeToNow();
8182
return session;
8283
}
8384
}

src/FlaUI.WebDriver/Controllers/FindElementsController.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,7 @@ private Session GetSession(string sessionId)
210210
{
211211
throw WebDriverResponseException.SessionNotFound(sessionId);
212212
}
213+
session.SetLastCommandTimeToNow();
213214
return session;
214215
}
215216
}

src/FlaUI.WebDriver/Controllers/ScreenshotController.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ private Session GetSession(string sessionId)
7575
{
7676
throw WebDriverResponseException.SessionNotFound(sessionId);
7777
}
78+
session.SetLastCommandTimeToNow();
7879
return session;
7980
}
8081
}

src/FlaUI.WebDriver/Controllers/SessionController.cs

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ public async Task<ActionResult> CreateNewSession([FromBody] CreateSessionRequest
2929
{
3030
var possibleCapabilities = GetPossibleCapabilities(request);
3131
var matchingCapabilities = possibleCapabilities.Where(
32-
capabilities => capabilities.TryGetValue("platformName", out var platformName) && platformName.ToLowerInvariant() == "windows"
32+
capabilities => capabilities.TryGetValue("platformName", out var platformName) && platformName.GetString()?.ToLowerInvariant() == "windows"
3333
);
3434

3535
Core.Application? app;
@@ -44,16 +44,16 @@ public async Task<ActionResult> CreateNewSession([FromBody] CreateSessionRequest
4444
}
4545
if (capabilities.TryGetValue("appium:app", out var appPath))
4646
{
47-
if (appPath == "Root")
47+
if (appPath.GetString() == "Root")
4848
{
4949
app = null;
5050
}
5151
else
5252
{
53-
capabilities.TryGetValue("appium:appArguments", out var appArguments);
53+
bool hasArguments = capabilities.TryGetValue("appium:appArguments", out var appArguments);
5454
try
5555
{
56-
var processStartInfo = new ProcessStartInfo(appPath, appArguments ?? "");
56+
var processStartInfo = new ProcessStartInfo(appPath.GetString()!, hasArguments ? appArguments.GetString()! : "");
5757
app = Core.Application.Launch(processStartInfo);
5858
}
5959
catch(Exception e)
@@ -64,19 +64,23 @@ public async Task<ActionResult> CreateNewSession([FromBody] CreateSessionRequest
6464
}
6565
else if(capabilities.TryGetValue("appium:appTopLevelWindow", out var appTopLevelWindowString))
6666
{
67-
Process process = GetProcessByMainWindowHandle(appTopLevelWindowString);
67+
Process process = GetProcessByMainWindowHandle(appTopLevelWindowString.GetString()!);
6868
app = Core.Application.Attach(process);
6969
}
7070
else if (capabilities.TryGetValue("appium:appTopLevelWindowTitleMatch", out var appTopLevelWindowTitleMatch))
7171
{
72-
Process? process = GetProcessByMainWindowTitle(appTopLevelWindowTitleMatch);
72+
Process? process = GetProcessByMainWindowTitle(appTopLevelWindowTitleMatch.GetString()!);
7373
app = Core.Application.Attach(process);
7474
}
7575
else
7676
{
7777
throw WebDriverResponseException.InvalidArgument("One of appium:app, appium:appTopLevelWindow or appium:appTopLevelWindowTitleMatch must be passed as a capability");
7878
}
7979
var session = new Session(app);
80+
if(capabilities.TryGetValue("appium:newCommandTimeout", out var newCommandTimeout))
81+
{
82+
session.NewCommandTimeout = TimeSpan.FromSeconds(newCommandTimeout.GetDouble());
83+
}
8084
_sessionRepository.Add(session);
8185
_logger.LogInformation("Created session with ID {SessionId} and capabilities {Capabilities}", session.SessionId, capabilities);
8286
return await Task.FromResult(WebDriverResult.Success(new CreateSessionResponse()
@@ -132,14 +136,14 @@ private static Process GetProcessByMainWindowHandle(string appTopLevelWindowStri
132136
return process;
133137
}
134138

135-
private static IEnumerable<Dictionary<string, string>> GetPossibleCapabilities(CreateSessionRequest request)
139+
private static IEnumerable<Dictionary<string, JsonElement>> GetPossibleCapabilities(CreateSessionRequest request)
136140
{
137-
var requiredCapabilities = request.Capabilities.AlwaysMatch ?? new Dictionary<string, string>();
138-
var allFirstMatchCapabilities = request.Capabilities.FirstMatch ?? new List<Dictionary<string, string>>(new[] { new Dictionary<string, string>() });
141+
var requiredCapabilities = request.Capabilities.AlwaysMatch ?? new Dictionary<string, JsonElement>();
142+
var allFirstMatchCapabilities = request.Capabilities.FirstMatch ?? new List<Dictionary<string, JsonElement>>(new[] { new Dictionary<string, JsonElement>() });
139143
return allFirstMatchCapabilities.Select(firstMatchCapabilities => MergeCapabilities(firstMatchCapabilities, requiredCapabilities));
140144
}
141145

142-
private static Dictionary<string, string> MergeCapabilities(Dictionary<string, string> firstMatchCapabilities, Dictionary<string, string> requiredCapabilities)
146+
private static Dictionary<string, JsonElement> MergeCapabilities(Dictionary<string, JsonElement> firstMatchCapabilities, Dictionary<string, JsonElement> requiredCapabilities)
143147
{
144148
var duplicateKeys = firstMatchCapabilities.Keys.Intersect(requiredCapabilities.Keys);
145149
if (duplicateKeys.Any())
@@ -176,6 +180,7 @@ private Session GetSession(string sessionId)
176180
{
177181
throw WebDriverResponseException.SessionNotFound(sessionId);
178182
}
183+
session.SetLastCommandTimeToNow();
179184
return session;
180185
}
181186
}

src/FlaUI.WebDriver/Controllers/TimeoutsController.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ private Session GetSession(string sessionId)
4242
{
4343
throw WebDriverResponseException.SessionNotFound(sessionId);
4444
}
45+
session.SetLastCommandTimeToNow();
4546
return session;
4647
}
4748
}

0 commit comments

Comments
 (0)