Skip to content

Commit

Permalink
[dotnet] Annotate nullability on platform-specific WebDrivers (#15236)
Browse files Browse the repository at this point in the history
* [dotnet] Annotate nullability on platform-specific WebDrivers

* Use new Response not-null helper
  • Loading branch information
RenderMichael authored Feb 9, 2025
1 parent a44f1d7 commit e639050
Show file tree
Hide file tree
Showing 7 changed files with 167 additions and 75 deletions.
10 changes: 9 additions & 1 deletion dotnet/src/webdriver/Chrome/ChromeDriver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
using System.Collections.Generic;
using System.Collections.ObjectModel;

#nullable enable

namespace OpenQA.Selenium.Chrome
{
/// <summary>
Expand Down Expand Up @@ -60,7 +62,7 @@ namespace OpenQA.Selenium.Chrome
/// </example>
public class ChromeDriver : ChromiumDriver
{
private static Dictionary<string, CommandInfo> chromeCustomCommands = new Dictionary<string, CommandInfo>()
private static readonly Dictionary<string, CommandInfo> chromeCustomCommands = new Dictionary<string, CommandInfo>()
{
{ ExecuteCdp, new HttpCommandInfo(HttpCommandInfo.PostCommand, "/session/{sessionId}/goog/cdp/execute") },
{ GetCastSinksCommand, new HttpCommandInfo(HttpCommandInfo.GetCommand, "/session/{sessionId}/goog/cast/get_sinks") },
Expand All @@ -83,6 +85,7 @@ public ChromeDriver()
/// Initializes a new instance of the <see cref="ChromeDriver"/> class using the specified options.
/// </summary>
/// <param name="options">The <see cref="ChromeOptions"/> to be used with the Chrome driver.</param>
/// <exception cref="ArgumentNullException">If <paramref name="options"/> is <see langword="null"/>.</exception>
public ChromeDriver(ChromeOptions options)
: this(ChromeDriverService.CreateDefaultService(), options, RemoteWebDriver.DefaultCommandTimeout)
{
Expand All @@ -92,6 +95,7 @@ public ChromeDriver(ChromeOptions options)
/// Initializes a new instance of the <see cref="ChromeDriver"/> class using the specified driver service.
/// </summary>
/// <param name="service">The <see cref="ChromeDriverService"/> used to initialize the driver.</param>
/// <exception cref="ArgumentNullException">If <paramref name="service"/> is <see langword="null"/>.</exception>
public ChromeDriver(ChromeDriverService service)
: this(service, new ChromeOptions())
{
Expand All @@ -113,6 +117,7 @@ public ChromeDriver(string chromeDriverDirectory)
/// </summary>
/// <param name="chromeDriverDirectory">The full path to the directory containing ChromeDriver.exe.</param>
/// <param name="options">The <see cref="ChromeOptions"/> to be used with the Chrome driver.</param>
/// <exception cref="ArgumentNullException">If <paramref name="options"/> is <see langword="null"/>.</exception>
public ChromeDriver(string chromeDriverDirectory, ChromeOptions options)
: this(chromeDriverDirectory, options, RemoteWebDriver.DefaultCommandTimeout)
{
Expand All @@ -125,6 +130,7 @@ public ChromeDriver(string chromeDriverDirectory, ChromeOptions options)
/// <param name="chromeDriverDirectory">The full path to the directory containing ChromeDriver.exe.</param>
/// <param name="options">The <see cref="ChromeOptions"/> to be used with the Chrome driver.</param>
/// <param name="commandTimeout">The maximum amount of time to wait for each command.</param>
/// <exception cref="ArgumentNullException">If <paramref name="options"/> is <see langword="null"/>.</exception>
public ChromeDriver(string chromeDriverDirectory, ChromeOptions options, TimeSpan commandTimeout)
: this(ChromeDriverService.CreateDefaultService(chromeDriverDirectory), options, commandTimeout)
{
Expand All @@ -136,6 +142,7 @@ public ChromeDriver(string chromeDriverDirectory, ChromeOptions options, TimeSpa
/// </summary>
/// <param name="service">The <see cref="ChromeDriverService"/> to use.</param>
/// <param name="options">The <see cref="ChromeOptions"/> used to initialize the driver.</param>
/// <exception cref="ArgumentNullException">If <paramref name="service"/> or <paramref name="options"/> are <see langword="null"/>.</exception>
public ChromeDriver(ChromeDriverService service, ChromeOptions options)
: this(service, options, RemoteWebDriver.DefaultCommandTimeout)
{
Expand All @@ -147,6 +154,7 @@ public ChromeDriver(ChromeDriverService service, ChromeOptions options)
/// <param name="service">The <see cref="ChromeDriverService"/> to use.</param>
/// <param name="options">The <see cref="ChromeOptions"/> to be used with the Chrome driver.</param>
/// <param name="commandTimeout">The maximum amount of time to wait for each command.</param>
/// <exception cref="ArgumentNullException">If <paramref name="service"/> or <paramref name="options"/> are <see langword="null"/>.</exception>
public ChromeDriver(ChromeDriverService service, ChromeOptions options, TimeSpan commandTimeout)
: base(service, options, commandTimeout)
{
Expand Down
78 changes: 48 additions & 30 deletions dotnet/src/webdriver/Chromium/ChromiumDriver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,12 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Threading.Tasks;

#nullable enable

namespace OpenQA.Selenium.Chromium
{
/// <summary>
Expand Down Expand Up @@ -108,9 +111,9 @@ public class ChromiumDriver : WebDriver, ISupportsLogs, IDevTools
public static readonly string SetPermissionCommand = "setPermission";

private readonly string optionsCapabilityName;
private DevToolsSession devToolsSession;
private DevToolsSession? devToolsSession;

private static Dictionary<string, CommandInfo> chromiumCustomCommands = new Dictionary<string, CommandInfo>()
private static readonly Dictionary<string, CommandInfo> chromiumCustomCommands = new Dictionary<string, CommandInfo>()
{
{ GetNetworkConditionsCommand, new HttpCommandInfo(HttpCommandInfo.GetCommand, "/session/{sessionId}/chromium/network_conditions") },
{ SetNetworkConditionsCommand, new HttpCommandInfo(HttpCommandInfo.PostCommand, "/session/{sessionId}/chromium/network_conditions") },
Expand All @@ -127,19 +130,18 @@ public class ChromiumDriver : WebDriver, ISupportsLogs, IDevTools
/// <param name="service">The <see cref="ChromiumDriverService"/> to use.</param>
/// <param name="options">The <see cref="ChromiumOptions"/> to be used with the ChromiumDriver.</param>
/// <param name="commandTimeout">The maximum amount of time to wait for each command.</param>
/// <exception cref="ArgumentNullException">If <paramref name="service"/> or <paramref name="options"/> are <see langword="null"/>.</exception>
/// <exception cref="ArgumentException">If the Chromium options capability name is <see langword="null"/>.</exception>
protected ChromiumDriver(ChromiumDriverService service, ChromiumOptions options, TimeSpan commandTimeout)
: base(GenerateDriverServiceCommandExecutor(service, options, commandTimeout), ConvertOptionsToCapabilities(options))
{
this.optionsCapabilityName = options.CapabilityName;
this.optionsCapabilityName = options.CapabilityName ?? throw new ArgumentException("No chromium options capability name specified", nameof(options));
}

/// <summary>
/// Gets the dictionary of custom Chromium commands registered with the driver.
/// </summary>
protected static IReadOnlyDictionary<string, CommandInfo> ChromiumCustomCommands
{
get { return new ReadOnlyDictionary<string, CommandInfo>(chromiumCustomCommands); }
}
protected static IReadOnlyDictionary<string, CommandInfo> ChromiumCustomCommands => new ReadOnlyDictionary<string, CommandInfo>(chromiumCustomCommands);

/// <summary>
/// Uses DriverFinder to set Service attributes if necessary when creating the command executor
Expand All @@ -150,6 +152,16 @@ protected static IReadOnlyDictionary<string, CommandInfo> ChromiumCustomCommands
/// <returns></returns>
private static ICommandExecutor GenerateDriverServiceCommandExecutor(DriverService service, DriverOptions options, TimeSpan commandTimeout)
{
if (service is null)
{
throw new ArgumentNullException(nameof(service));
}

if (options is null)
{
throw new ArgumentNullException(nameof(options));
}

if (service.DriverServicePath == null)
{
DriverFinder finder = new DriverFinder(options);
Expand Down Expand Up @@ -177,27 +189,31 @@ private static ICommandExecutor GenerateDriverServiceCommandExecutor(DriverServi
/// in conjunction with a standalone WebDriver server.</remarks>
public override IFileDetector FileDetector
{
get { return base.FileDetector; }
get => base.FileDetector;
set { }
}

/// <summary>
/// Gets a value indicating whether a DevTools session is active.
/// </summary>
public bool HasActiveDevToolsSession
{
get { return this.devToolsSession != null; }
}
[MemberNotNullWhen(true, nameof(devToolsSession))]
public bool HasActiveDevToolsSession => this.devToolsSession != null;

/// <summary>
/// Gets or sets the network condition emulation for Chromium.
/// </summary>
/// <exception cref="ArgumentNullException">If the value is set to <see langword="null"/>.</exception>
public ChromiumNetworkConditions NetworkConditions
{
get
{
Response response = this.Execute(GetNetworkConditionsCommand, null);
return ChromiumNetworkConditions.FromDictionary(response.Value as Dictionary<string, object>);
if (response.Value is not Dictionary<string, object?> responseDictionary)
{
throw new WebDriverException($"GetNetworkConditions command returned successfully, but data was not an object: {response.Value}");
}

return ChromiumNetworkConditions.FromDictionary(responseDictionary);
}

set
Expand All @@ -209,6 +225,7 @@ public ChromiumNetworkConditions NetworkConditions

Dictionary<string, object> parameters = new Dictionary<string, object>();
parameters["network_conditions"] = value;

this.Execute(SetNetworkConditionsCommand, parameters);
}
}
Expand All @@ -217,6 +234,7 @@ public ChromiumNetworkConditions NetworkConditions
/// Launches a Chromium based application.
/// </summary>
/// <param name="id">ID of the chromium app to launch.</param>
/// <exception cref="ArgumentNullException">If <paramref name="id"/> is <see langword="null"/>.</exception>
public void LaunchApp(string id)
{
if (id == null)
Expand All @@ -226,6 +244,7 @@ public void LaunchApp(string id)

Dictionary<string, object> parameters = new Dictionary<string, object>();
parameters["id"] = id;

this.Execute(LaunchAppCommand, parameters);
}

Expand All @@ -234,6 +253,7 @@ public void LaunchApp(string id)
/// </summary>
/// <param name="permissionName">Name of item to set the permission on.</param>
/// <param name="permissionValue">Value to set the permission to.</param>
/// <exception cref="ArgumentNullException">If <paramref name="permissionName"/> or <paramref name="permissionValue"/> are <see langword="null"/>.</exception>
public void SetPermission(string permissionName, string permissionValue)
{
if (permissionName == null)
Expand All @@ -260,7 +280,8 @@ public void SetPermission(string permissionName, string permissionValue)
/// <param name="commandName">Name of the command to execute.</param>
/// <param name="commandParameters">Parameters of the command to execute.</param>
/// <returns>An object representing the result of the command, if applicable.</returns>
public object ExecuteCdpCommand(string commandName, Dictionary<string, object> commandParameters)
/// <exception cref="ArgumentNullException">If <paramref name="commandName"/> is <see langword="null"/>.</exception>
public object? ExecuteCdpCommand(string commandName, Dictionary<string, object> commandParameters)
{
if (commandName == null)
{
Expand Down Expand Up @@ -296,21 +317,20 @@ public DevToolsSession GetDevToolsSession(DevToolsOptions options)
throw new WebDriverException("Cannot find " + this.optionsCapabilityName + " capability for driver");
}

Dictionary<string, object> optionsCapability = this.Capabilities.GetCapability(this.optionsCapabilityName) as Dictionary<string, object>;
if (optionsCapability == null)
object? optionsCapabilityObject = this.Capabilities.GetCapability(this.optionsCapabilityName);
if (optionsCapabilityObject is not Dictionary<string, object?> optionsCapability)
{
throw new WebDriverException("Found " + this.optionsCapabilityName + " capability, but is not an object");
throw new WebDriverException($"Found {this.optionsCapabilityName} capability, but is not an object: {optionsCapabilityObject}");
}

if (!optionsCapability.ContainsKey("debuggerAddress"))
if (!optionsCapability.TryGetValue("debuggerAddress", out object? debuggerAddress))
{
throw new WebDriverException("Did not find debuggerAddress capability in " + this.optionsCapabilityName);
}

string debuggerAddress = optionsCapability["debuggerAddress"].ToString();
try
{
DevToolsSession session = new DevToolsSession(debuggerAddress, options);
DevToolsSession session = new DevToolsSession(debuggerAddress?.ToString(), options);
Task.Run(async () => await session.StartSession()).GetAwaiter().GetResult();
this.devToolsSession = session;
}
Expand Down Expand Up @@ -341,7 +361,7 @@ public void CloseDevToolsSession()
{
if (this.devToolsSession != null)
{
Task.Run(async () => await this.devToolsSession.StopSession(true)).GetAwaiter().GetResult();
Task.Run(async () => await this.devToolsSession.StopSession(manualDetach: true)).GetAwaiter().GetResult();
}
}

Expand All @@ -361,18 +381,16 @@ public List<Dictionary<string, string>> GetCastSinks()
{
List<Dictionary<string, string>> returnValue = new List<Dictionary<string, string>>();
Response response = this.Execute(GetCastSinksCommand, null);
object[] responseValue = response.Value as object[];
if (responseValue != null)
if (response.Value is object?[] responseValue)
{
foreach (object entry in responseValue)
foreach (object? entry in responseValue)
{
Dictionary<string, object> entryValue = entry as Dictionary<string, object>;
if (entryValue != null)
if (entry is Dictionary<string, object?> entryValue)
{
Dictionary<string, string> sink = new Dictionary<string, string>();
foreach (KeyValuePair<string, object> pair in entryValue)
foreach (KeyValuePair<string, object?> pair in entryValue)
{
sink[pair.Key] = pair.Value.ToString();
sink[pair.Key] = pair.Value!.ToString()!;
}

returnValue.Add(sink);
Expand Down Expand Up @@ -434,10 +452,10 @@ public void StartDesktopMirroring(string deviceName)
/// Returns the error message if there is any issue in a Cast session.
/// </summary>
/// <returns>An error message.</returns>
public String GetCastIssueMessage()
public string? GetCastIssueMessage()
{
Response response = this.Execute(GetCastIssueMessageCommand, null);
return (string)response.Value;
return (string?)response.Value;
}

/// <summary>
Expand Down
8 changes: 4 additions & 4 deletions dotnet/src/webdriver/Chromium/ChromiumNetworkConditions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -91,12 +91,12 @@ public long UploadThroughput
/// </summary>
/// <param name="dictionary">The dictionary to use to create the object.</param>
/// <returns>The ChromiumNetworkConditions object created from the dictionary.</returns>
public static ChromiumNetworkConditions FromDictionary(Dictionary<string, object> dictionary)
public static ChromiumNetworkConditions FromDictionary(Dictionary<string, object?> dictionary)
{
ChromiumNetworkConditions conditions = new ChromiumNetworkConditions();
if (dictionary.TryGetValue("offline", out object? offline))
{
conditions.IsOffline = (bool)offline;
conditions.IsOffline = (bool)offline!;
}

if (dictionary.TryGetValue("latency", out object? latency))
Expand All @@ -106,12 +106,12 @@ public static ChromiumNetworkConditions FromDictionary(Dictionary<string, object

if (dictionary.TryGetValue("upload_throughput", out object? uploadThroughput))
{
conditions.UploadThroughput = (long)uploadThroughput;
conditions.UploadThroughput = (long)uploadThroughput!;
}

if (dictionary.TryGetValue("download_throughput", out object? downloadThroughput))
{
conditions.DownloadThroughput = (long)downloadThroughput;
conditions.DownloadThroughput = (long)downloadThroughput!;
}

return conditions;
Expand Down
Loading

0 comments on commit e639050

Please sign in to comment.