Skip to content

Commit 286c139

Browse files
authoredJan 24, 2025··
[dotnet] Annotate nullability for DriverService and chromium/safari services (#15101)
* [dotnet] Annotate nullability for `DriverService` and some derived types * Default values to `null` instead of sentinel values.
1 parent d4ba7a5 commit 286c139

File tree

3 files changed

+85
-160
lines changed

3 files changed

+85
-160
lines changed
 

‎dotnet/src/webdriver/Chromium/ChromiumDriverService.cs

+38-75
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121
using System.Globalization;
2222
using System.Text;
2323

24+
#nullable enable
25+
2426
namespace OpenQA.Selenium.Chromium
2527
{
2628
/// <summary>
@@ -30,15 +32,6 @@ public abstract class ChromiumDriverService : DriverService
3032
{
3133
private const string DefaultChromeDriverServiceExecutableName = "chromedriver";
3234

33-
private string logPath = string.Empty;
34-
private string urlPathPrefix = string.Empty;
35-
private string portServerAddress = string.Empty;
36-
private string allowedIPAddresses = string.Empty;
37-
private int adbPort = -1;
38-
private bool disableBuildCheck;
39-
private bool enableVerboseLogging;
40-
private bool enableAppendLog;
41-
4235
/// <summary>
4336
/// Initializes a new instance of the <see cref="ChromiumDriverService"/> class.
4437
/// </summary>
@@ -51,94 +44,64 @@ protected ChromiumDriverService(string executablePath, string executableFileName
5144
}
5245

5346
/// <summary>
54-
/// Gets or sets the location of the log file written to by the ChromeDriver executable.
47+
/// <para>Gets or sets the location of the log file written to by the ChromeDriver executable.</para>
48+
/// <para><see langword="null"/> or <see cref="string.Empty"/> signify no log path.</para>
5549
/// </summary>
56-
public string LogPath
57-
{
58-
get { return this.logPath; }
59-
set { this.logPath = value; }
60-
}
50+
public string? LogPath { get; set; }
6151

6252
/// <summary>
63-
/// Gets or sets the base URL path prefix for commands (e.g., "wd/url").
53+
/// <para>Gets or sets the base URL path prefix for commands (e.g., "wd/url").</para>
54+
/// <para><see langword="null"/> or <see cref="string.Empty"/> signify no prefix.</para>
6455
/// </summary>
65-
public string UrlPathPrefix
66-
{
67-
get { return this.urlPathPrefix; }
68-
set { this.urlPathPrefix = value; }
69-
}
56+
public string? UrlPathPrefix { get; set; }
7057

7158
/// <summary>
72-
/// Gets or sets the address of a server to contact for reserving a port.
59+
/// <para>Gets or sets the address of a server to contact for reserving a port.</para>
60+
/// <para><see langword="null"/> or <see cref="string.Empty"/> signify no port server.</para>
7361
/// </summary>
74-
public string PortServerAddress
75-
{
76-
get { return this.portServerAddress; }
77-
set { this.portServerAddress = value; }
78-
}
62+
public string? PortServerAddress { get; set; }
7963

8064
/// <summary>
81-
/// Gets or sets the port on which the Android Debug Bridge is listening for commands.
65+
/// <para>Gets or sets the port on which the Android Debug Bridge is listening for commands.</para>
66+
/// <para>A value less than or equal to 0, or <see langword="null"/>, indicates no Android Debug Bridge specified.</para>
8267
/// </summary>
83-
public int AndroidDebugBridgePort
84-
{
85-
get { return this.adbPort; }
86-
set { this.adbPort = value; }
87-
}
68+
public int? AndroidDebugBridgePort { get; set; }
8869

8970
/// <summary>
9071
/// Gets or sets a value indicating whether to skip version compatibility check
9172
/// between the driver and the browser.
9273
/// Defaults to <see langword="false"/>.
9374
/// </summary>
94-
public bool DisableBuildCheck
95-
{
96-
get { return this.disableBuildCheck; }
97-
set { this.disableBuildCheck = value; }
98-
}
75+
public bool DisableBuildCheck { get; set; }
9976

10077
/// <summary>
10178
/// Gets or sets a value indicating whether to enable verbose logging for the ChromeDriver executable.
10279
/// Defaults to <see langword="false"/>.
10380
/// </summary>
104-
public bool EnableVerboseLogging
105-
{
106-
get { return this.enableVerboseLogging; }
107-
set { this.enableVerboseLogging = value; }
108-
}
81+
public bool EnableVerboseLogging { get; set; }
10982

11083
/// <summary>
11184
/// Gets or sets a value indicating whether to enable appending to an existing ChromeDriver log file.
11285
/// Defaults to <see langword="false"/>.
11386
/// </summary>
114-
public bool EnableAppendLog
115-
{
116-
get { return this.enableAppendLog; }
117-
set { this.enableAppendLog = value; }
118-
}
87+
public bool EnableAppendLog { get; set; }
11988

12089
/// <summary>
121-
/// Gets or sets the comma-delimited list of IP addresses that are approved to
122-
/// connect to this instance of the Chrome driver. Defaults to an empty string,
123-
/// which means only the local loopback address can connect.
90+
/// <para>Gets or sets the comma-delimited list of IP addresses that are approved to connect to this instance of the Chrome driver.</para>
91+
/// <para>A value of <see langword="null"/> or <see cref="string.Empty"/> means only the local loopback address can connect.</para>
12492
/// </summary>
12593
[Obsolete($"Use {nameof(AllowedIPAddresses)}")]
126-
public string WhitelistedIPAddresses
94+
public string? WhitelistedIPAddresses
12795
{
128-
get { return this.allowedIPAddresses; }
129-
set { this.allowedIPAddresses = value; }
96+
get => this.AllowedIPAddresses;
97+
set => this.AllowedIPAddresses = value;
13098
}
13199

132100
/// <summary>
133-
/// Gets or sets the comma-delimited list of IP addresses that are approved to
134-
/// connect to this instance of the Chrome driver. Defaults to an empty string,
135-
/// which means only the local loopback address can connect.
101+
/// <para>Gets or sets the comma-delimited list of IP addresses that are approved to connect to this instance of the Chrome driver.</para>
102+
/// <para>A value of <see langword="null"/> or <see cref="string.Empty"/> means only the local loopback address can connect.</para>
136103
/// </summary>
137-
public string AllowedIPAddresses
138-
{
139-
get => this.allowedIPAddresses;
140-
set => this.allowedIPAddresses = value;
141-
}
104+
public string? AllowedIPAddresses { get; set; }
142105

143106
/// <summary>
144107
/// Gets the command-line arguments for the driver service.
@@ -148,49 +111,49 @@ protected override string CommandLineArguments
148111
get
149112
{
150113
StringBuilder argsBuilder = new StringBuilder(base.CommandLineArguments);
151-
if (this.adbPort > 0)
114+
if (this.AndroidDebugBridgePort is int adb && adb > 0)
152115
{
153-
argsBuilder.AppendFormat(CultureInfo.InvariantCulture, " --adb-port={0}", this.adbPort);
116+
argsBuilder.AppendFormat(CultureInfo.InvariantCulture, " --adb-port={0}", adb);
154117
}
155118

156119
if (this.SuppressInitialDiagnosticInformation)
157120
{
158121
argsBuilder.Append(" --silent");
159122
}
160123

161-
if (this.disableBuildCheck)
124+
if (this.DisableBuildCheck)
162125
{
163126
argsBuilder.Append(" --disable-build-check");
164127
}
165128

166-
if (this.enableVerboseLogging)
129+
if (this.EnableVerboseLogging)
167130
{
168131
argsBuilder.Append(" --verbose");
169132
}
170133

171-
if (this.enableAppendLog)
134+
if (this.EnableAppendLog)
172135
{
173136
argsBuilder.Append(" --append-log");
174137
}
175138

176-
if (!string.IsNullOrEmpty(this.logPath))
139+
if (!string.IsNullOrEmpty(this.LogPath))
177140
{
178-
argsBuilder.AppendFormat(CultureInfo.InvariantCulture, " --log-path=\"{0}\"", this.logPath);
141+
argsBuilder.AppendFormat(CultureInfo.InvariantCulture, " --log-path=\"{0}\"", this.LogPath);
179142
}
180143

181-
if (!string.IsNullOrEmpty(this.urlPathPrefix))
144+
if (!string.IsNullOrEmpty(this.UrlPathPrefix))
182145
{
183-
argsBuilder.AppendFormat(CultureInfo.InvariantCulture, " --url-base={0}", this.urlPathPrefix);
146+
argsBuilder.AppendFormat(CultureInfo.InvariantCulture, " --url-base={0}", this.UrlPathPrefix);
184147
}
185148

186-
if (!string.IsNullOrEmpty(this.portServerAddress))
149+
if (!string.IsNullOrEmpty(this.PortServerAddress))
187150
{
188-
argsBuilder.AppendFormat(CultureInfo.InvariantCulture, " --port-server={0}", this.portServerAddress);
151+
argsBuilder.AppendFormat(CultureInfo.InvariantCulture, " --port-server={0}", this.PortServerAddress);
189152
}
190153

191-
if (!string.IsNullOrEmpty(this.allowedIPAddresses))
154+
if (!string.IsNullOrEmpty(this.AllowedIPAddresses))
192155
{
193-
argsBuilder.Append(string.Format(CultureInfo.InvariantCulture, " -allowed-ips={0}", this.allowedIPAddresses));
156+
argsBuilder.Append(string.Format(CultureInfo.InvariantCulture, " -allowed-ips={0}", this.AllowedIPAddresses));
194157
}
195158

196159
return argsBuilder.ToString();

‎dotnet/src/webdriver/DriverService.cs

+40-80
Original file line numberDiff line numberDiff line change
@@ -20,28 +20,24 @@
2020
using OpenQA.Selenium.Remote;
2121
using System;
2222
using System.Diagnostics;
23+
using System.Diagnostics.CodeAnalysis;
2324
using System.Globalization;
2425
using System.IO;
2526
using System.Net;
2627
using System.Net.Http;
2728
using System.Threading.Tasks;
2829

30+
#nullable enable
31+
2932
namespace OpenQA.Selenium
3033
{
3134
/// <summary>
3235
/// Exposes the service provided by a native WebDriver server executable.
3336
/// </summary>
3437
public abstract class DriverService : ICommandServer
3538
{
36-
private string driverServicePath;
37-
private string driverServiceExecutableName;
38-
private string driverServiceHostName = "localhost";
39-
private int driverServicePort;
40-
private bool silent;
41-
private bool hideCommandPromptWindow;
4239
private bool isDisposed;
43-
private Process driverServiceProcess;
44-
private TimeSpan initializationTimeout = TimeSpan.FromSeconds(20);
40+
private Process? driverServiceProcess;
4541

4642
/// <summary>
4743
/// Initializes a new instance of the <see cref="DriverService"/> class.
@@ -55,29 +51,33 @@ public abstract class DriverService : ICommandServer
5551
/// <exception cref="DriverServiceNotFoundException">
5652
/// If the specified driver service executable does not exist in the specified directory.
5753
/// </exception>
58-
protected DriverService(string servicePath, int port, string driverServiceExecutableName)
54+
protected DriverService(string? servicePath, int port, string? driverServiceExecutableName)
5955
{
60-
this.driverServicePath = servicePath;
61-
this.driverServiceExecutableName = driverServiceExecutableName;
62-
this.driverServicePort = port;
56+
this.DriverServicePath = servicePath;
57+
this.DriverServiceExecutableName = driverServiceExecutableName;
58+
this.Port = port;
6359
}
6460

6561
/// <summary>
6662
/// Occurs when the driver process is starting.
6763
/// </summary>
68-
public event EventHandler<DriverProcessStartingEventArgs> DriverProcessStarting;
64+
public event EventHandler<DriverProcessStartingEventArgs>? DriverProcessStarting;
6965

7066
/// <summary>
7167
/// Occurs when the driver process has completely started.
7268
/// </summary>
73-
public event EventHandler<DriverProcessStartedEventArgs> DriverProcessStarted;
69+
public event EventHandler<DriverProcessStartedEventArgs>? DriverProcessStarted;
7470

7571
/// <summary>
7672
/// Gets the Uri of the service.
7773
/// </summary>
7874
public Uri ServiceUrl
7975
{
80-
get { return new Uri(string.Format(CultureInfo.InvariantCulture, "http://{0}:{1}", this.driverServiceHostName, this.driverServicePort)); }
76+
get
77+
{
78+
string url = string.Format(CultureInfo.InvariantCulture, "http://{0}:{1}", this.HostName, this.Port);
79+
return new Uri(url);
80+
}
8181
}
8282

8383
/// <summary>
@@ -88,48 +88,30 @@ public Uri ServiceUrl
8888
/// (non-local) machines. This property can be used as a workaround so
8989
/// that an IP address (like "127.0.0.1" or "::1") can be used instead.
9090
/// </remarks>
91-
public string HostName
92-
{
93-
get { return this.driverServiceHostName; }
94-
set { this.driverServiceHostName = value; }
95-
}
91+
public string HostName { get; set; } = "localhost";
9692

9793
/// <summary>
9894
/// Gets or sets the port of the service.
9995
/// </summary>
100-
public int Port
101-
{
102-
get { return this.driverServicePort; }
103-
set { this.driverServicePort = value; }
104-
}
96+
public int Port { get; set; }
10597

10698
/// <summary>
10799
/// Gets or sets a value indicating whether the initial diagnostic information is suppressed
108100
/// when starting the driver server executable. Defaults to <see langword="false"/>, meaning
109101
/// diagnostic information should be shown by the driver server executable.
110102
/// </summary>
111-
public bool SuppressInitialDiagnosticInformation
112-
{
113-
get { return this.silent; }
114-
set { this.silent = value; }
115-
}
103+
public bool SuppressInitialDiagnosticInformation { get; set; }
116104

117105
/// <summary>
118106
/// Gets a value indicating whether the service is running.
119107
/// </summary>
120-
public bool IsRunning
121-
{
122-
get { return this.driverServiceProcess != null && !this.driverServiceProcess.HasExited; }
123-
}
108+
[MemberNotNullWhen(true, nameof(driverServiceProcess))]
109+
public bool IsRunning => this.driverServiceProcess != null && !this.driverServiceProcess.HasExited;
124110

125111
/// <summary>
126112
/// Gets or sets a value indicating whether the command prompt window of the service should be hidden.
127113
/// </summary>
128-
public bool HideCommandPromptWindow
129-
{
130-
get { return this.hideCommandPromptWindow; }
131-
set { this.hideCommandPromptWindow = value; }
132-
}
114+
public bool HideCommandPromptWindow { get; set; }
133115

134116
/// <summary>
135117
/// Gets the process ID of the running driver service executable. Returns 0 if the process is not running.
@@ -159,54 +141,33 @@ public int ProcessId
159141
/// <summary>
160142
/// Gets or sets a value indicating the time to wait for an initial connection before timing out.
161143
/// </summary>
162-
public TimeSpan InitializationTimeout
163-
{
164-
get { return this.initializationTimeout; }
165-
set { this.initializationTimeout = value; }
166-
}
144+
public TimeSpan InitializationTimeout { get; set; } = TimeSpan.FromSeconds(20);
167145

168146
/// <summary>
169147
/// Gets or sets the executable file name of the driver service.
170148
/// </summary>
171-
public string DriverServiceExecutableName
172-
{
173-
get { return this.driverServiceExecutableName; }
174-
set { this.driverServiceExecutableName = value; }
175-
}
149+
public string? DriverServiceExecutableName { get; set; }
176150

177151
/// <summary>
178152
/// Gets or sets the path of the driver service.
179153
/// </summary>
180-
public string DriverServicePath
181-
{
182-
get { return this.driverServicePath; }
183-
set { this.driverServicePath = value; }
184-
}
154+
public string? DriverServicePath { get; set; }
185155

186156
/// <summary>
187157
/// Gets the command-line arguments for the driver service.
188158
/// </summary>
189-
protected virtual string CommandLineArguments
190-
{
191-
get { return string.Format(CultureInfo.InvariantCulture, "--port={0}", this.driverServicePort); }
192-
}
159+
protected virtual string CommandLineArguments => string.Format(CultureInfo.InvariantCulture, "--port={0}", this.Port);
193160

194161
/// <summary>
195162
/// Gets a value indicating the time to wait for the service to terminate before forcing it to terminate.
196163
/// </summary>
197-
protected virtual TimeSpan TerminationTimeout
198-
{
199-
get { return TimeSpan.FromSeconds(10); }
200-
}
164+
protected virtual TimeSpan TerminationTimeout => TimeSpan.FromSeconds(10);
201165

202166
/// <summary>
203167
/// Gets a value indicating whether the service has a shutdown API that can be called to terminate
204168
/// it gracefully before forcing a termination.
205169
/// </summary>
206-
protected virtual bool HasShutdown
207-
{
208-
get { return true; }
209-
}
170+
protected virtual bool HasShutdown => true;
210171

211172
/// <summary>
212173
/// Gets a value indicating whether the service is responding to HTTP requests.
@@ -231,7 +192,7 @@ protected virtual bool IsInitialized
231192
// that the HTTP status returned is a 200 status, and that the resposne has the correct
232193
// Content-Type header. A more sophisticated check would parse the JSON response and
233194
// validate its values. At the moment we do not do this more sophisticated check.
234-
isInitialized = response.StatusCode == HttpStatusCode.OK && response.Content.Headers.ContentType.MediaType.StartsWith("application/json", StringComparison.OrdinalIgnoreCase);
195+
isInitialized = response.StatusCode == HttpStatusCode.OK && response.Content.Headers.ContentType is { MediaType: string mediaType } && mediaType.StartsWith("application/json", StringComparison.OrdinalIgnoreCase);
235196
}
236197
}
237198
}
@@ -256,6 +217,7 @@ public void Dispose()
256217
/// <summary>
257218
/// Starts the DriverService if it is not already running.
258219
/// </summary>
220+
[MemberNotNull(nameof(driverServiceProcess))]
259221
public void Start()
260222
{
261223
if (this.driverServiceProcess != null)
@@ -265,9 +227,14 @@ public void Start()
265227

266228
this.driverServiceProcess = new Process();
267229

268-
if (this.driverServicePath != null)
230+
if (this.DriverServicePath != null)
269231
{
270-
this.driverServiceProcess.StartInfo.FileName = Path.Combine(this.driverServicePath, this.driverServiceExecutableName);
232+
if (this.DriverServiceExecutableName is null)
233+
{
234+
throw new InvalidOperationException("If the driver service path is specified, the driver service executable name must be as well");
235+
}
236+
237+
this.driverServiceProcess.StartInfo.FileName = Path.Combine(this.DriverServicePath, this.DriverServiceExecutableName);
271238
}
272239
else
273240
{
@@ -276,7 +243,7 @@ public void Start()
276243

277244
this.driverServiceProcess.StartInfo.Arguments = this.CommandLineArguments;
278245
this.driverServiceProcess.StartInfo.UseShellExecute = false;
279-
this.driverServiceProcess.StartInfo.CreateNoWindow = this.hideCommandPromptWindow;
246+
this.driverServiceProcess.StartInfo.CreateNoWindow = this.HideCommandPromptWindow;
280247

281248
DriverProcessStartingEventArgs eventArgs = new DriverProcessStartingEventArgs(this.driverServiceProcess.StartInfo);
282249
this.OnDriverProcessStarting(eventArgs);
@@ -288,8 +255,7 @@ public void Start()
288255

289256
if (!serviceAvailable)
290257
{
291-
string msg = "Cannot start the driver service on " + this.ServiceUrl;
292-
throw new WebDriverException(msg);
258+
throw new WebDriverException($"Cannot start the driver service on {this.ServiceUrl}");
293259
}
294260
}
295261

@@ -327,10 +293,7 @@ protected void OnDriverProcessStarting(DriverProcessStartingEventArgs eventArgs)
327293
throw new ArgumentNullException(nameof(eventArgs), "eventArgs must not be null");
328294
}
329295

330-
if (this.DriverProcessStarting != null)
331-
{
332-
this.DriverProcessStarting(this, eventArgs);
333-
}
296+
this.DriverProcessStarting?.Invoke(this, eventArgs);
334297
}
335298

336299
/// <summary>
@@ -344,10 +307,7 @@ protected void OnDriverProcessStarted(DriverProcessStartedEventArgs eventArgs)
344307
throw new ArgumentNullException(nameof(eventArgs), "eventArgs must not be null");
345308
}
346309

347-
if (this.DriverProcessStarted != null)
348-
{
349-
this.DriverProcessStarted(this, eventArgs);
350-
}
310+
this.DriverProcessStarted?.Invoke(this, eventArgs);
351311
}
352312

353313
/// <summary>

‎dotnet/src/webdriver/Safari/SafariDriverService.cs

+7-5
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@
2525
using System.Text;
2626
using System.Threading.Tasks;
2727

28+
#nullable enable
29+
2830
namespace OpenQA.Selenium.Safari
2931
{
3032
/// <summary>
@@ -37,10 +39,10 @@ public sealed class SafariDriverService : DriverService
3739
/// <summary>
3840
/// Initializes a new instance of the <see cref="SafariDriverService"/> class.
3941
/// </summary>
40-
/// <param name="executablePath">The full path to the SafariDriver executable.</param>
42+
/// <param name="executablePath">The directory of the SafariDriver executable.</param>
4143
/// <param name="executableFileName">The file name of the SafariDriver executable.</param>
4244
/// <param name="port">The port on which the SafariDriver executable should listen.</param>
43-
private SafariDriverService(string executablePath, string executableFileName, int port)
45+
private SafariDriverService(string? executablePath, string? executableFileName, int port)
4446
: base(executablePath, port, executableFileName)
4547
{
4648
}
@@ -73,7 +75,7 @@ protected override TimeSpan TerminationTimeout
7375
// because the executable does not have a clean shutdown command,
7476
// which means we have to kill the process. Using a short timeout
7577
// gets us to the termination point much faster.
76-
get { return TimeSpan.FromMilliseconds(100); }
78+
get => TimeSpan.FromMilliseconds(100);
7779
}
7880

7981
/// <summary>
@@ -84,7 +86,7 @@ protected override bool HasShutdown
8486
{
8587
// The Safari driver executable does not have a clean shutdown command,
8688
// which means we have to kill the process.
87-
get { return false; }
89+
get => false;
8890
}
8991

9092
/// <summary>
@@ -107,7 +109,7 @@ public static SafariDriverService CreateDefaultService(string driverPath)
107109
if (File.Exists(driverPath))
108110
{
109111
fileName = Path.GetFileName(driverPath);
110-
driverPath = Path.GetDirectoryName(driverPath);
112+
driverPath = Path.GetDirectoryName(driverPath)!;
111113
}
112114
else
113115
{

0 commit comments

Comments
 (0)
Please sign in to comment.