Skip to content

Commit ef46bca

Browse files
committed
Updating .NET Firefox driver to use standard init and terminate
This commit modifies the Marionette driver to test for initialization using a URL poll instead of a hard-coded sleep. We poll for the URL to delete a nonexistent session. This still throws an error, but one that still lets us know that the HTTP server is available and responding to HTTP requests. We also now terminate the Firefox driver executable immediately rather than attempting to send a shutdown command. Since the Mozilla implementation slavishly follows the specification, and there is no server management API in the spec, nor is there likely to be, no matter how useful such a thing would be, we will settle for forcing the executable process to terminate. This is a suboptimal approach, but is required by the current architecture.
1 parent 742f7a8 commit ef46bca

File tree

2 files changed

+89
-26
lines changed

2 files changed

+89
-26
lines changed

dotnet/src/webdriver/DriverService.cs

Lines changed: 27 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -160,19 +160,27 @@ protected virtual string CommandLineArguments
160160
/// <summary>
161161
/// Gets a value indicating the time to wait for an initial connection before timing out.
162162
/// </summary>
163-
protected virtual TimeSpan InitialConnectionTimeout
163+
protected virtual TimeSpan InitializationTimeout
164164
{
165165
get { return TimeSpan.FromSeconds(20); }
166166
}
167167

168+
/// <summary>
169+
/// Gets a value indicating the time to wait for the service to terminate before forcing it to terminate.
170+
/// </summary>
171+
protected virtual TimeSpan TerminationTimeout
172+
{
173+
get { return TimeSpan.FromSeconds(10); }
174+
}
175+
168176
/// <summary>
169177
/// Gets a value indicating whether the service is responding to HTTP requests.
170178
/// </summary>
171-
protected virtual bool IsAvailable
179+
protected virtual bool IsInitialized
172180
{
173181
get
174182
{
175-
bool isAvailable = false;
183+
bool isInitialized = false;
176184
try
177185
{
178186
Uri serviceHealthUri = new Uri(this.ServiceUrl, new Uri(DriverCommand.Status, UriKind.Relative));
@@ -185,15 +193,15 @@ protected virtual bool IsAvailable
185193
// that the HTTP status returned is a 200 status, and that the resposne has the correct
186194
// Content-Type header. A more sophisticated check would parse the JSON response and
187195
// validate its values. At the moment we do not do this more sophisticated check.
188-
isAvailable = response.StatusCode == HttpStatusCode.OK && response.ContentType.StartsWith("application/json", StringComparison.OrdinalIgnoreCase);
196+
isInitialized = response.StatusCode == HttpStatusCode.OK && response.ContentType.StartsWith("application/json", StringComparison.OrdinalIgnoreCase);
189197
response.Close();
190198
}
191199
catch (WebException ex)
192200
{
193201
Console.WriteLine(ex.Message);
194202
}
195203

196-
return isAvailable;
204+
return isInitialized;
197205
}
198206
}
199207

@@ -218,7 +226,7 @@ public void Start()
218226
this.driverServiceProcess.StartInfo.UseShellExecute = false;
219227
this.driverServiceProcess.StartInfo.CreateNoWindow = this.hideCommandPromptWindow;
220228
this.driverServiceProcess.Start();
221-
bool serviceAvailable = WaitForServiceAvailable();
229+
bool serviceAvailable = WaitForServiceInitialization();
222230

223231
if (!serviceAvailable)
224232
{
@@ -265,12 +273,11 @@ protected virtual void Dispose(bool disposing)
265273
[SecurityPermission(SecurityAction.Demand)]
266274
private void Stop()
267275
{
268-
if (this.driverServiceProcess != null && !this.driverServiceProcess.HasExited)
276+
if (this.IsRunning)
269277
{
270278
Uri shutdownUrl = new Uri(this.ServiceUrl, "/shutdown");
271-
DateTime timeout = DateTime.Now.Add(TimeSpan.FromSeconds(10));
272-
bool processStopped = false;
273-
while (!processStopped && DateTime.Now < timeout)
279+
DateTime timeout = DateTime.Now.Add(this.TerminationTimeout);
280+
while (this.IsRunning && DateTime.Now < timeout)
274281
{
275282
try
276283
{
@@ -284,20 +291,18 @@ private void Stop()
284291
HttpWebResponse response = request.GetResponse() as HttpWebResponse;
285292
response.Close();
286293
this.driverServiceProcess.WaitForExit(3000);
287-
processStopped = this.driverServiceProcess.HasExited;
288294
}
289295
catch (WebException)
290296
{
291-
processStopped = true;
292297
}
293298
}
294299

295300
// If at this point, the process still hasn't exited, wait for one
296301
// last-ditch time, then, if it still hasn't exited, kill it. Note
297302
// that falling into this branch of code should be exceedingly rare.
298-
if (!this.driverServiceProcess.HasExited)
303+
if (this.IsRunning)
299304
{
300-
this.driverServiceProcess.WaitForExit(5000);
305+
this.driverServiceProcess.WaitForExit(Convert.ToInt32(this.TerminationTimeout.TotalMilliseconds));
301306
if (!this.driverServiceProcess.HasExited)
302307
{
303308
this.driverServiceProcess.Kill();
@@ -310,27 +315,27 @@ private void Stop()
310315
}
311316

312317
/// <summary>
313-
/// Waits until a the service is available, or the timeout set
314-
/// by the <see cref="InitialConnectionTimeout"/> property is reached.
318+
/// Waits until a the service is initialized, or the timeout set
319+
/// by the <see cref="InitializationTimeout"/> property is reached.
315320
/// </summary>
316321
/// <returns><see langword="true"/> if the service is properly started and receiving HTTP requests;
317322
/// otherwise; <see langword="false"/>.</returns>
318-
private bool WaitForServiceAvailable()
323+
private bool WaitForServiceInitialization()
319324
{
320-
bool isAvailable = false;
321-
DateTime timeout = DateTime.Now.Add(this.InitialConnectionTimeout);
322-
while (!isAvailable && DateTime.Now < timeout)
325+
bool isInitialized = false;
326+
DateTime timeout = DateTime.Now.Add(this.InitializationTimeout);
327+
while (!isInitialized && DateTime.Now < timeout)
323328
{
324329
// If the driver service process has exited, we can exit early.
325330
if (!this.IsRunning)
326331
{
327332
break;
328333
}
329334

330-
isAvailable = this.IsAvailable;
335+
isInitialized = this.IsInitialized;
331336
}
332337

333-
return isAvailable;
338+
return isInitialized;
334339
}
335340
}
336341
}

dotnet/src/webdriver/Firefox/FirefoxDriverService.cs

Lines changed: 62 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@
2222
using System.IO;
2323
using System.Text;
2424
using OpenQA.Selenium.Internal;
25+
using System.Net;
26+
using OpenQA.Selenium.Remote;
2527

2628
namespace OpenQA.Selenium.Firefox
2729
{
@@ -67,20 +69,76 @@ public int BrowserCommunicationPort
6769
/// <summary>
6870
/// Gets a value indicating the time to wait for an initial connection before timing out.
6971
/// </summary>
70-
protected override TimeSpan InitialConnectionTimeout
72+
protected override TimeSpan InitializationTimeout
7173
{
7274
get { return TimeSpan.FromSeconds(2); }
7375
}
7476

77+
/// <summary>
78+
/// Gets a value indicating the time to wait for the service to terminate before forcing it to terminate.
79+
/// </summary>
80+
protected override TimeSpan TerminationTimeout
81+
{
82+
// Use a very small timeout for terminating the Firefox driver,
83+
// because the executable does not have a clean shutdown command,
84+
// which means we have to kill the process. Using a short timeout
85+
// gets us to the termination point much faster.
86+
get { return TimeSpan.FromMilliseconds(100); }
87+
}
88+
7589
/// <summary>
7690
/// Gets a value indicating whether the service is responding to HTTP requests.
7791
/// </summary>
78-
protected override bool IsAvailable
92+
protected override bool IsInitialized
7993
{
8094
get
8195
{
82-
System.Threading.Thread.Sleep(InitialConnectionTimeout);
83-
return true;
96+
bool isInitialized = false;
97+
try
98+
{
99+
// Since Firefox driver won't implement the /session end point (because
100+
// the W3C spec working group stupidly decided that it isn't necessary),
101+
// we'll attempt to poll for a different URL which has no side effects.
102+
// We've chosen to poll on the "quit" URL, passing in a nonexistent
103+
// session id.
104+
Uri serviceHealthUri = new Uri(this.ServiceUrl, new Uri("/session/FakeSessionIdForPollingPurposes", UriKind.Relative));
105+
HttpWebRequest request = HttpWebRequest.Create(serviceHealthUri) as HttpWebRequest;
106+
request.KeepAlive = false;
107+
request.Timeout = 5000;
108+
request.Method = "DELETE";
109+
HttpWebResponse response = request.GetResponse() as HttpWebResponse;
110+
111+
// Checking the response from deleting a nonexistent session. Note that we are simply
112+
// checking that the HTTP status returned is a 200 status, and that the resposne has
113+
// the correct Content-Type header. A more sophisticated check would parse the JSON
114+
// response and validate its values. At the moment we do not do this more sophisticated
115+
// check.
116+
isInitialized = response.StatusCode == HttpStatusCode.OK && response.ContentType.StartsWith("application/json", StringComparison.OrdinalIgnoreCase);
117+
response.Close();
118+
}
119+
catch (WebException ex)
120+
{
121+
// Because the Firefox driver (incorrectly) does not allow quit on a
122+
// nonexistent session to succeed, this will throw a WebException,
123+
// which means we're reduced to using exception handling for flow control.
124+
// This situation is highly undesirable, and in fact is a horrible code
125+
// smell, but the implementation leaves us no choice. So we will check for
126+
// the known response code and content type header, just like we would for
127+
// the success case. Either way, a valid HTTP response instead of a socket
128+
// error would tell us that the HTTP server is responding to requests, which
129+
// is really what we want anyway.
130+
HttpWebResponse errorResponse = ex.Response as HttpWebResponse;
131+
if (errorResponse != null)
132+
{
133+
isInitialized = errorResponse.StatusCode == HttpStatusCode.InternalServerError && errorResponse.ContentType.StartsWith("application/json", StringComparison.OrdinalIgnoreCase);
134+
}
135+
else
136+
{
137+
Console.WriteLine(ex.Message);
138+
}
139+
}
140+
141+
return isInitialized;
84142
}
85143
}
86144

0 commit comments

Comments
 (0)