diff --git a/dotnet/src/webdriver/By.cs b/dotnet/src/webdriver/By.cs index 73b57da66c87d..e0f498931ed64 100644 --- a/dotnet/src/webdriver/By.cs +++ b/dotnet/src/webdriver/By.cs @@ -24,6 +24,8 @@ using System.Globalization; using System.Text.RegularExpressions; +#nullable enable + namespace OpenQA.Selenium { /// @@ -37,17 +39,11 @@ namespace OpenQA.Selenium [Serializable] public class By { - private static readonly string CssSelectorMechanism = "css selector"; - private static readonly string XPathSelectorMechanism = "xpath"; - private static readonly string TagNameMechanism = "tag name"; - private static readonly string LinkTextMechanism = "link text"; - private static readonly string PartialLinkTextMechanism = "partial link text"; - - private string description = "OpenQA.Selenium.By"; - private string mechanism = string.Empty; - private string criteria = string.Empty; - private Func findElementMethod; - private Func> findElementsMethod; + private const string CssSelectorMechanism = "css selector"; + private const string XPathSelectorMechanism = "xpath"; + private const string TagNameMechanism = "tag name"; + private const string LinkTextMechanism = "link text"; + private const string PartialLinkTextMechanism = "partial link text"; /// /// Initializes a new instance of the class. @@ -57,20 +53,20 @@ protected By() } /// - /// Intializes a new instance of the class using the specified mechanism and critieria for finding elements. + /// Initializes a new instance of the class using the specified mechanism and criteria for finding elements. /// /// The mechanism to use in finding elements. /// The criteria to use in finding elements. /// - /// Customizing nothing else, instances using this constructor will attempt to find elemsnts + /// Customizing nothing else, instances using this constructor will attempt to find elements /// using the method, taking string arguments. /// protected By(string mechanism, string criteria) { - this.mechanism = mechanism; - this.criteria = criteria; - this.findElementMethod = (ISearchContext context) => ((IFindsElement)context).FindElement(this.mechanism, this.criteria); - this.findElementsMethod = (ISearchContext context) => ((IFindsElement)context).FindElements(this.mechanism, this.criteria); + this.Mechanism = mechanism; + this.Criteria = criteria; + this.FindElementMethod = (ISearchContext context) => ((IFindsElement)context).FindElement(this.Mechanism, this.Criteria); + this.FindElementsMethod = (ISearchContext context) => ((IFindsElement)context).FindElements(this.Mechanism, this.Criteria); } /// @@ -83,52 +79,34 @@ protected By(string mechanism, string criteria) /// IWebElements/>. protected By(Func findElementMethod, Func> findElementsMethod) { - this.findElementMethod = findElementMethod; - this.findElementsMethod = findElementsMethod; + this.FindElementMethod = findElementMethod; + this.FindElementsMethod = findElementsMethod; } /// /// Gets the value of the mechanism for this class instance. /// - public string Mechanism - { - get { return this.mechanism; } - } + public string Mechanism { get; } = string.Empty; /// /// Gets the value of the criteria for this class instance. /// - public string Criteria - { - get { return this.criteria; } - } + public string Criteria { get; } = string.Empty; /// /// Gets or sets the value of the description for this class instance. /// - protected string Description - { - get { return this.description; } - set { this.description = value; } - } + protected string Description { get; set; } = "OpenQA.Selenium.By"; /// /// Gets or sets the method used to find a single element matching specified criteria. /// - protected Func FindElementMethod - { - get { return this.findElementMethod; } - set { this.findElementMethod = value; } - } + protected Func? FindElementMethod { get; set; } /// /// Gets or sets the method used to find all elements matching specified criteria. /// - protected Func> FindElementsMethod - { - get { return this.findElementsMethod; } - set { this.findElementsMethod = value; } - } + protected Func>? FindElementsMethod { get; set; } /// /// Determines if two instances are equal. @@ -136,16 +114,16 @@ protected Func> FindElementsMeth /// One instance to compare. /// The other instance to compare. /// if the two instances are equal; otherwise, . - public static bool operator ==(By one, By two) + public static bool operator ==(By? one, By? two) { // If both are null, or both are same instance, return true. - if (object.ReferenceEquals(one, two)) + if (ReferenceEquals(one, two)) { return true; } // If one is null, but not both, return false. - if (((object)one == null) || ((object)two == null)) + if ((one is null) || (two is null)) { return false; } @@ -159,7 +137,7 @@ protected Func> FindElementsMeth /// One instance to compare. /// The other instance to compare. /// if the two instances are not equal; otherwise, . - public static bool operator !=(By one, By two) + public static bool operator !=(By? one, By? two) { return !(one == two); } @@ -169,6 +147,7 @@ protected Func> FindElementsMeth /// /// The ID to find. /// A object the driver can use to find the elements. + /// If is . public static By Id(string idToFind) { if (idToFind == null) @@ -176,16 +155,16 @@ public static By Id(string idToFind) throw new ArgumentNullException(nameof(idToFind), "Cannot find elements with a null id attribute."); } - string selector = By.EscapeCssSelector(idToFind); + string selector = EscapeCssSelector(idToFind); By by = new By(CssSelectorMechanism, "#" + selector); - by.description = "By.Id: " + idToFind; + by.Description = "By.Id: " + idToFind; if (string.IsNullOrEmpty(selector)) { // Finding multiple elements with an empty ID will return // an empty list. However, finding by a CSS selector of '#' // throws an exception, even in the multiple elements case, // which means we need to short-circuit that behavior. - by.findElementsMethod = (ISearchContext context) => new List().AsReadOnly(); + by.FindElementsMethod = (ISearchContext context) => new List().AsReadOnly(); } return by; @@ -196,6 +175,7 @@ public static By Id(string idToFind) /// /// The link text to find. /// A object the driver can use to find the elements. + /// If is null. public static By LinkText(string linkTextToFind) { if (linkTextToFind == null) @@ -203,9 +183,10 @@ public static By LinkText(string linkTextToFind) throw new ArgumentNullException(nameof(linkTextToFind), "Cannot find elements when link text is null."); } - By by = new By(LinkTextMechanism, linkTextToFind); - by.description = "By.LinkText: " + linkTextToFind; - return by; + return new By(LinkTextMechanism, linkTextToFind) + { + Description = "By.LinkText: " + linkTextToFind + }; } /// @@ -213,6 +194,7 @@ public static By LinkText(string linkTextToFind) /// /// The name to find. /// A object the driver can use to find the elements. + /// If is null. public static By Name(string nameToFind) { if (nameToFind == null) @@ -220,10 +202,10 @@ public static By Name(string nameToFind) throw new ArgumentNullException(nameof(nameToFind), "Cannot find elements when name text is null."); } - string selector = "*[name =\"" + By.EscapeCssSelector(nameToFind) + "\"]"; - By by = new By(CssSelectorMechanism, selector); - by.description = "By.Name: " + nameToFind; - return by; + return new By(CssSelectorMechanism, $"*[name =\"{EscapeCssSelector(nameToFind)}\"]") + { + Description = "By.Name: " + nameToFind + }; } /// @@ -234,6 +216,7 @@ public static By Name(string nameToFind) /// /// The XPath query to use. /// A object the driver can use to find the elements. + /// If is null. public static By XPath(string xpathToFind) { if (xpathToFind == null) @@ -241,9 +224,10 @@ public static By XPath(string xpathToFind) throw new ArgumentNullException(nameof(xpathToFind), "Cannot find elements when the XPath expression is null."); } - By by = new By(XPathSelectorMechanism, xpathToFind); - by.description = "By.XPath: " + xpathToFind; - return by; + return new By(XPathSelectorMechanism, xpathToFind) + { + Description = "By.XPath: " + xpathToFind + }; } /// @@ -254,6 +238,7 @@ public static By XPath(string xpathToFind) /// If an element has many classes then this will match against each of them. /// For example if the value is "one two onone", then the following values for the /// className parameter will match: "one" and "two". + /// If is null. public static By ClassName(string classNameToFind) { if (classNameToFind == null) @@ -261,19 +246,20 @@ public static By ClassName(string classNameToFind) throw new ArgumentNullException(nameof(classNameToFind), "Cannot find elements when the class name expression is null."); } - string selector = "." + By.EscapeCssSelector(classNameToFind); + string selector = "." + EscapeCssSelector(classNameToFind); if (selector.Contains(" ")) { // Finding elements by class name with whitespace is not allowed. // However, converting the single class name to a valid CSS selector // by prepending a '.' may result in a still-valid, but incorrect - // selector. Thus, we short-ciruit that behavior here. + // selector. Thus, we short-circuit that behavior here. throw new InvalidSelectorException("Compound class names not allowed. Cannot have whitespace in class name. Use CSS selectors instead."); } - By by = new By(CssSelectorMechanism, selector); - by.description = "By.ClassName[Contains]: " + classNameToFind; - return by; + return new By(CssSelectorMechanism, selector) + { + Description = "By.ClassName[Contains]: " + classNameToFind + }; } /// @@ -281,6 +267,7 @@ public static By ClassName(string classNameToFind) /// /// The partial link text to find. /// A object the driver can use to find the elements. + /// If is null. public static By PartialLinkText(string partialLinkTextToFind) { if (partialLinkTextToFind == null) @@ -288,9 +275,10 @@ public static By PartialLinkText(string partialLinkTextToFind) throw new ArgumentNullException(nameof(partialLinkTextToFind), "Cannot find elements when partial link text is null."); } - By by = new By(PartialLinkTextMechanism, partialLinkTextToFind); - by.description = "By.PartialLinkText: " + partialLinkTextToFind; - return by; + return new By(PartialLinkTextMechanism, partialLinkTextToFind) + { + Description = "By.PartialLinkText: " + partialLinkTextToFind + }; } /// @@ -298,6 +286,7 @@ public static By PartialLinkText(string partialLinkTextToFind) /// /// The tag name to find. /// A object the driver can use to find the elements. + /// If is null. public static By TagName(string tagNameToFind) { if (tagNameToFind == null) @@ -305,9 +294,10 @@ public static By TagName(string tagNameToFind) throw new ArgumentNullException(nameof(tagNameToFind), "Cannot find elements when name tag name is null."); } - By by = new By(TagNameMechanism, tagNameToFind); - by.description = "By.TagName: " + tagNameToFind; - return by; + return new By(TagNameMechanism, tagNameToFind) + { + Description = "By.TagName: " + tagNameToFind + }; } /// @@ -315,6 +305,7 @@ public static By TagName(string tagNameToFind) /// /// The CSS selector to find. /// A object the driver can use to find the elements. + /// If is null. public static By CssSelector(string cssSelectorToFind) { if (cssSelectorToFind == null) @@ -322,9 +313,10 @@ public static By CssSelector(string cssSelectorToFind) throw new ArgumentNullException(nameof(cssSelectorToFind), "Cannot find elements when name CSS selector is null."); } - By by = new By(CssSelectorMechanism, cssSelectorToFind); - by.description = "By.CssSelector: " + cssSelectorToFind; - return by; + return new By(CssSelectorMechanism, cssSelectorToFind) + { + Description = "By.CssSelector: " + cssSelectorToFind + }; } /// @@ -334,7 +326,12 @@ public static By CssSelector(string cssSelectorToFind) /// The first matching on the current context. public virtual IWebElement FindElement(ISearchContext context) { - return this.findElementMethod(context); + if (this.FindElementMethod is not { } findElementMethod) + { + throw new InvalidOperationException("FindElement method not set. Override the By.FindElement method, set the By.FindElementMethod property, or use a constructor that sets a query mechanism."); + } + + return findElementMethod(context); } /// @@ -345,7 +342,12 @@ public virtual IWebElement FindElement(ISearchContext context) /// matching the current criteria, or an empty list if nothing matches. public virtual ReadOnlyCollection FindElements(ISearchContext context) { - return this.findElementsMethod(context); + if (this.FindElementsMethod is not { } findElementsMethod) + { + throw new InvalidOperationException("FindElements method not set. Override the By.FindElements method, set the By.FindElementsMethod property, or use a constructor that sets a query mechanism."); + } + + return findElementsMethod(context); } /// @@ -354,7 +356,7 @@ public virtual ReadOnlyCollection FindElements(ISearchContext conte /// The string displaying the finder content. public override string ToString() { - return this.description; + return this.Description; } /// @@ -366,12 +368,12 @@ public override string ToString() /// if the specified Object /// is equal to the current Object; otherwise, /// . - public override bool Equals(object obj) + public override bool Equals(object? obj) { var other = obj as By; // TODO(dawagner): This isn't ideal - return other != null && this.description.Equals(other.description); + return other != null && this.Description.Equals(other.Description); } /// @@ -380,7 +382,7 @@ public override bool Equals(object obj) /// A hash code for the current Object. public override int GetHashCode() { - return this.description.GetHashCode(); + return this.Description.GetHashCode(); } /// @@ -390,13 +392,17 @@ public override int GetHashCode() /// The selector with invalid characters escaped. internal static string EscapeCssSelector(string selector) { - string escaped = Regex.Replace(selector, @"([ '""\\#.:;,!?+<>=~*^$|%&@`{}\-/\[\]\(\)])", @"\$1"); + string escaped = InvalidCharsRegex.Replace(selector, @"\$1"); if (selector.Length > 0 && char.IsDigit(selector[0])) { - escaped = @"\" + (30 + int.Parse(selector.Substring(0, 1), CultureInfo.InvariantCulture)).ToString(CultureInfo.InvariantCulture) + " " + selector.Substring(1); + int digitCode = 30 + int.Parse(selector.Substring(0, 1), CultureInfo.InvariantCulture); + + escaped = $"\\{digitCode.ToString(CultureInfo.InvariantCulture)} {selector.Substring(1)}"; } return escaped; } + + private static readonly Regex InvalidCharsRegex = new Regex(@"([ '""\\#.:;,!?+<>=~*^$|%&@`{}\-/\[\]\(\)])", RegexOptions.Compiled); } } diff --git a/dotnet/src/webdriver/Chromium/ChromiumDriver.cs b/dotnet/src/webdriver/Chromium/ChromiumDriver.cs index f683bec9c2eab..9cfdb5e5a5d66 100644 --- a/dotnet/src/webdriver/Chromium/ChromiumDriver.cs +++ b/dotnet/src/webdriver/Chromium/ChromiumDriver.cs @@ -330,7 +330,7 @@ public DevToolsSession GetDevToolsSession(DevToolsOptions options) try { - DevToolsSession session = new DevToolsSession(debuggerAddress?.ToString(), options); + DevToolsSession session = new DevToolsSession(debuggerAddress?.ToString()!, options); Task.Run(async () => await session.StartSession()).GetAwaiter().GetResult(); this.devToolsSession = session; } diff --git a/dotnet/src/webdriver/Cookie.cs b/dotnet/src/webdriver/Cookie.cs index dac30e0522629..242c841e4c695 100644 --- a/dotnet/src/webdriver/Cookie.cs +++ b/dotnet/src/webdriver/Cookie.cs @@ -24,6 +24,8 @@ using System.Linq; using System.Text.Json.Serialization; +#nullable enable + namespace OpenQA.Selenium { /// @@ -32,15 +34,20 @@ namespace OpenQA.Selenium [Serializable] public class Cookie { - private string cookieName; - private string cookieValue; - private string cookiePath; - private string cookieDomain; - private string sameSite; - private bool isHttpOnly; - private bool secure; - private DateTime? cookieExpiry; - private readonly string[] sameSiteValues = { "Strict", "Lax", "None" }; + private readonly string cookieName; + private readonly string cookieValue; + private readonly string? cookiePath; + private readonly string? cookieDomain; + private readonly string? sameSite; + private readonly bool isHttpOnly; + private readonly bool secure; + private readonly DateTime? cookieExpiry; + private readonly HashSet sameSiteValues = new HashSet() + { + "Strict", + "Lax", + "None" + }; /// /// Initializes a new instance of the class with a specific name and value. @@ -65,7 +72,7 @@ public Cookie(string name, string value) /// If the name is or an empty string, /// or if it contains a semi-colon. /// If the value is . - public Cookie(string name, string value, string path) + public Cookie(string name, string value, string? path) : this(name, value, path, null) { } @@ -81,7 +88,7 @@ public Cookie(string name, string value, string path) /// If the name is or an empty string, /// or if it contains a semi-colon. /// If the value is . - public Cookie(string name, string value, string path, DateTime? expiry) + public Cookie(string name, string value, string? path, DateTime? expiry) : this(name, value, null, path, expiry) { } @@ -98,7 +105,7 @@ public Cookie(string name, string value, string path, DateTime? expiry) /// If the name is or an empty string, /// or if it contains a semi-colon. /// If the value is . - public Cookie(string name, string value, string domain, string path, DateTime? expiry) + public Cookie(string name, string value, string? domain, string? path, DateTime? expiry) : this(name, value, domain, path, expiry, false, false, null) { } @@ -118,7 +125,7 @@ public Cookie(string name, string value, string domain, string path, DateTime? e /// If the name and value are both an empty string, /// if the name contains a semi-colon, or if same site value is not valid. /// If the name, value or currentUrl is . - public Cookie(string name, string value, string domain, string path, DateTime? expiry, bool secure, bool isHttpOnly, string sameSite) + public Cookie(string name, string value, string? domain, string? path, DateTime? expiry, bool secure, bool isHttpOnly, string? sameSite) { if (name == null) { @@ -135,7 +142,7 @@ public Cookie(string name, string value, string domain, string path, DateTime? e throw new ArgumentException("Cookie name and value cannot both be empty string"); } - if (name.IndexOf(';') != -1) + if (name.Contains(';')) { throw new ArgumentException("Cookie names cannot contain a ';': " + name, nameof(name)); } @@ -172,77 +179,52 @@ public Cookie(string name, string value, string domain, string path, DateTime? e /// Gets the name of the cookie. /// [JsonPropertyName("name")] - public string Name - { - get { return this.cookieName; } - } + public string Name => this.cookieName; /// /// Gets the value of the cookie. /// [JsonPropertyName("value")] - public string Value - { - get { return this.cookieValue; } - } + public string Value => this.cookieValue; /// /// Gets the domain of the cookie. /// [JsonPropertyName("domain")] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - public string Domain - { - get { return this.cookieDomain; } - } + public string? Domain => this.cookieDomain; /// /// Gets the path of the cookie. /// [JsonPropertyName("path")] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - public virtual string Path - { - get { return this.cookiePath; } - } + public virtual string? Path => this.cookiePath; /// /// Gets a value indicating whether the cookie is secure. /// [JsonPropertyName("secure")] - public virtual bool Secure - { - get { return this.secure; } - } + public virtual bool Secure => this.secure; /// /// Gets a value indicating whether the cookie is an HTTP-only cookie. /// [JsonPropertyName("httpOnly")] - public virtual bool IsHttpOnly - { - get { return this.isHttpOnly; } - - } + public virtual bool IsHttpOnly => this.isHttpOnly; /// /// Gets the SameSite setting for the cookie. /// [JsonPropertyName("sameSite")] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - public virtual string SameSite - { - get { return this.sameSite; } - } + public virtual string? SameSite => this.sameSite; /// /// Gets the expiration date of the cookie. /// [JsonIgnore] - public DateTime? Expiry - { - get { return this.cookieExpiry; } - } + public DateTime? Expiry => this.cookieExpiry; /// /// Gets the cookie expiration date in seconds from the defined zero date (01 January 1970 00:00:00 UTC). @@ -273,54 +255,54 @@ internal long? ExpirySeconds /// /// The Dictionary object containing the cookie parameters. /// A object with the proper parameters set. - public static Cookie FromDictionary(Dictionary rawCookie) + public static Cookie FromDictionary(Dictionary rawCookie) { if (rawCookie == null) { throw new ArgumentNullException(nameof(rawCookie)); } - string name = rawCookie["name"].ToString(); + string name = rawCookie["name"]!.ToString()!; string value = string.Empty; - if (rawCookie["value"] != null) + if (rawCookie.TryGetValue("value", out object? valueObj)) { - value = rawCookie["value"].ToString(); + value = valueObj!.ToString()!; } string path = "/"; - if (rawCookie.ContainsKey("path") && rawCookie["path"] != null) + if (rawCookie.TryGetValue("path", out object? pathObj) && pathObj != null) { - path = rawCookie["path"].ToString(); + path = pathObj.ToString()!; } string domain = string.Empty; - if (rawCookie.ContainsKey("domain") && rawCookie["domain"] != null) + if (rawCookie.TryGetValue("domain", out object? domainObj) && domainObj != null) { - domain = rawCookie["domain"].ToString(); + domain = domainObj.ToString()!; } DateTime? expires = null; - if (rawCookie.ContainsKey("expiry") && rawCookie["expiry"] != null) + if (rawCookie.TryGetValue("expiry", out object? expiryObj) && expiryObj != null) { - expires = ConvertExpirationTime(rawCookie["expiry"].ToString()); + expires = ConvertExpirationTime(expiryObj.ToString()!); } bool secure = false; - if (rawCookie.ContainsKey("secure") && rawCookie["secure"] != null) + if (rawCookie.TryGetValue("secure", out object? secureObj) && secureObj != null) { - secure = bool.Parse(rawCookie["secure"].ToString()); + secure = bool.Parse(secureObj.ToString()!); } bool isHttpOnly = false; - if (rawCookie.ContainsKey("httpOnly") && rawCookie["httpOnly"] != null) + if (rawCookie.TryGetValue("httpOnly", out object? httpOnlyObj) && httpOnlyObj != null) { - isHttpOnly = bool.Parse(rawCookie["httpOnly"].ToString()); + isHttpOnly = bool.Parse(httpOnlyObj.ToString()!); } - string sameSite = null; - if (rawCookie.ContainsKey("sameSite") && rawCookie["sameSite"] != null) + string? sameSite = null; + if (rawCookie.TryGetValue("sameSite", out object? sameSiteObj)) { - sameSite = rawCookie["sameSite"].ToString(); + sameSite = sameSiteObj?.ToString(); } return new ReturnedCookie(name, value, domain, path, expires, secure, isHttpOnly, sameSite); @@ -348,7 +330,7 @@ public override string ToString() /// if the specified Object /// is equal to the current Object; otherwise, /// . - public override bool Equals(object obj) + public override bool Equals(object? obj) { // Two cookies are equal if the name and value match if (this == obj) @@ -378,16 +360,15 @@ public override int GetHashCode() return this.cookieName.GetHashCode(); } - private static string StripPort(string domain) + private static string? StripPort(string? domain) { - return string.IsNullOrEmpty(domain) ? null : domain.Split(':')[0]; + return string.IsNullOrEmpty(domain) ? null : domain!.Split(':')[0]; } private static DateTime? ConvertExpirationTime(string expirationTime) { DateTime? expires = null; - double seconds = 0; - if (double.TryParse(expirationTime, NumberStyles.Number, CultureInfo.InvariantCulture, out seconds)) + if (double.TryParse(expirationTime, NumberStyles.Number, CultureInfo.InvariantCulture, out double seconds)) { try { diff --git a/dotnet/src/webdriver/DevTools/DevToolsSession.cs b/dotnet/src/webdriver/DevTools/DevToolsSession.cs index 946afa47f0ffa..bc332a8fcfb99 100644 --- a/dotnet/src/webdriver/DevTools/DevToolsSession.cs +++ b/dotnet/src/webdriver/DevTools/DevToolsSession.cs @@ -496,7 +496,7 @@ private async Task InitializeSocketConnection() LogTrace("Creating WebSocket"); this.connection = new WebSocketConnection(this.openConnectionWaitTimeSpan, this.closeConnectionWaitTimeSpan); connection.DataReceived += OnConnectionDataReceived; - await connection.Start(this.EndpointAddress).ConfigureAwait(false); + await connection.Start(this.EndpointAddress!).ConfigureAwait(false); LogTrace("WebSocket created"); } diff --git a/dotnet/src/webdriver/DomMutationData.cs b/dotnet/src/webdriver/DomMutationData.cs index d15e5e36fca46..84b2e8ad499a3 100644 --- a/dotnet/src/webdriver/DomMutationData.cs +++ b/dotnet/src/webdriver/DomMutationData.cs @@ -57,10 +57,10 @@ public class DomMutationData public string AttributeOriginalValue { get; internal set; } = null!; /// - /// Stores the element associated with the target ID + /// Stores the element associated with the target ID, if any. /// [JsonIgnore] - public IWebElement Element { get; internal set; } = null!; // Set internally + public IWebElement? Element { get; internal set; } /// /// Returns a string that represents the current object. diff --git a/dotnet/src/webdriver/IE/InternetExplorerDriverLogLevel.cs b/dotnet/src/webdriver/IE/InternetExplorerDriverLogLevel.cs index 840c7497bcf7e..5778e40ed9549 100644 --- a/dotnet/src/webdriver/IE/InternetExplorerDriverLogLevel.cs +++ b/dotnet/src/webdriver/IE/InternetExplorerDriverLogLevel.cs @@ -17,6 +17,8 @@ // under the License. // +#nullable enable + namespace OpenQA.Selenium.IE { /// diff --git a/dotnet/src/webdriver/IE/InternetExplorerOptions.cs b/dotnet/src/webdriver/IE/InternetExplorerOptions.cs index b6142ab859165..0251e0c5debce 100644 --- a/dotnet/src/webdriver/IE/InternetExplorerOptions.cs +++ b/dotnet/src/webdriver/IE/InternetExplorerOptions.cs @@ -20,6 +20,8 @@ using System; using System.Collections.Generic; +#nullable enable + namespace OpenQA.Selenium.IE { /// @@ -92,27 +94,8 @@ public class InternetExplorerOptions : DriverOptions private const string LegacyFileUploadDialogHandlingCapability = "ie.useLegacyFileUploadDialogHandling"; private const string AttachToEdgeChromeCapability = "ie.edgechromium"; private const string IgnoreProcessMatchCapability = "ie.ignoreprocessmatch"; - - private bool ignoreProtectedModeSettings; - private bool ignoreZoomLevel; - private bool enableNativeEvents = true; - private bool requireWindowFocus; - private bool enablePersistentHover = true; - private bool forceCreateProcessApi; - private bool forceShellWindowsApi; - private bool usePerProcessProxy; - private bool ensureCleanSession; - private bool enableFullPageScreenshot = true; - private bool legacyFileUploadDialogHandling; - private bool attachToEdgeChrome; - private bool ignoreProcessMatch; - private TimeSpan browserAttachTimeout = TimeSpan.MinValue; - private TimeSpan fileUploadDialogTimeout = TimeSpan.MinValue; - private string initialBrowserUrl = string.Empty; - private string browserCommandLineArguments = string.Empty; - private string edgeExecutablePath = string.Empty; - private InternetExplorerElementScrollBehavior elementScrollBehavior = InternetExplorerElementScrollBehavior.Default; - private Dictionary additionalInternetExplorerOptions = new Dictionary(); + private readonly bool enableFullPageScreenshot = true; + private readonly Dictionary additionalInternetExplorerOptions = new Dictionary(); /// /// Initializes a new instance of the class. @@ -147,38 +130,22 @@ public InternetExplorerOptions() : base() /// /// Gets or sets a value indicating whether to ignore the settings of the Internet Explorer Protected Mode. /// - public bool IntroduceInstabilityByIgnoringProtectedModeSettings - { - get { return this.ignoreProtectedModeSettings; } - set { this.ignoreProtectedModeSettings = value; } - } + public bool IntroduceInstabilityByIgnoringProtectedModeSettings { get; set; } /// /// Gets or sets a value indicating whether to ignore the zoom level of Internet Explorer . /// - public bool IgnoreZoomLevel - { - get { return this.ignoreZoomLevel; } - set { this.ignoreZoomLevel = value; } - } + public bool IgnoreZoomLevel { get; set; } /// /// Gets or sets a value indicating whether to use native events in interacting with elements. /// - public bool EnableNativeEvents - { - get { return this.enableNativeEvents; } - set { this.enableNativeEvents = value; } - } + public bool EnableNativeEvents { get; set; } = true; /// /// Gets or sets a value indicating whether to require the browser window to have focus before interacting with elements. /// - public bool RequireWindowFocus - { - get { return this.requireWindowFocus; } - set { this.requireWindowFocus = value; } - } + public bool RequireWindowFocus { get; set; } /// /// Gets or sets the initial URL displayed when IE is launched. If not set, the browser launches @@ -190,82 +157,50 @@ public bool RequireWindowFocus /// to avoid the flakiness introduced by ignoring the Protected Mode settings. Nevertheless, setting Protected Mode /// zone settings to the same value in the IE configuration is the preferred method. /// - public string InitialBrowserUrl - { - get { return this.initialBrowserUrl; } - set { this.initialBrowserUrl = value; } - } + public string? InitialBrowserUrl { get; set; } /// /// Gets or sets the value for describing how elements are scrolled into view in the IE driver. Defaults /// to scrolling the element to the top of the viewport. /// - public InternetExplorerElementScrollBehavior ElementScrollBehavior - { - get { return this.elementScrollBehavior; } - set { this.elementScrollBehavior = value; } - } + public InternetExplorerElementScrollBehavior ElementScrollBehavior { get; set; } = InternetExplorerElementScrollBehavior.Default; /// /// Gets or sets a value indicating whether to enable persistently sending WM_MOUSEMOVE messages /// to the IE window during a mouse hover. /// - public bool EnablePersistentHover - { - get { return this.enablePersistentHover; } - set { this.enablePersistentHover = value; } - } + public bool EnablePersistentHover { get; set; } = true; /// /// Gets or sets the amount of time the driver will attempt to look for a newly launched instance /// of Internet Explorer. /// - public TimeSpan BrowserAttachTimeout - { - get { return this.browserAttachTimeout; } - set { this.browserAttachTimeout = value; } - } + public TimeSpan BrowserAttachTimeout { get; set; } = TimeSpan.MinValue; /// /// Gets or sets the amount of time the driver will attempt to look for the file selection /// dialog when attempting to upload a file. /// - public TimeSpan FileUploadDialogTimeout - { - get { return this.fileUploadDialogTimeout; } - set { this.fileUploadDialogTimeout = value; } - } + public TimeSpan FileUploadDialogTimeout { get; set; } = TimeSpan.MinValue; /// /// Gets or sets a value indicating whether to force the use of the Windows CreateProcess API /// when launching Internet Explorer. The default value is . /// - public bool ForceCreateProcessApi - { - get { return this.forceCreateProcessApi; } - set { this.forceCreateProcessApi = value; } - } + public bool ForceCreateProcessApi { get; set; } /// /// Gets or sets a value indicating whether to force the use of the Windows ShellWindows API /// when attaching to Internet Explorer. The default value is . /// - public bool ForceShellWindowsApi - { - get { return this.forceShellWindowsApi; } - set { this.forceShellWindowsApi = value; } - } + public bool ForceShellWindowsApi { get; set; } /// /// Gets or sets the command line arguments used in launching Internet Explorer when the /// Windows CreateProcess API is used. This property only has an effect when the /// is . /// - public string BrowserCommandLineArguments - { - get { return this.browserCommandLineArguments; } - set { this.browserCommandLineArguments = value; } - } + public string? BrowserCommandLineArguments { get; set; } /// /// Gets or sets a value indicating whether to use the supplied @@ -275,11 +210,7 @@ public string BrowserCommandLineArguments /// , or , and is /// otherwise ignored. Defaults to . /// - public bool UsePerProcessProxy - { - get { return this.usePerProcessProxy; } - set { this.usePerProcessProxy = value; } - } + public bool UsePerProcessProxy { get; set; } /// /// Gets or sets a value indicating whether to clear the Internet Explorer cache @@ -287,47 +218,27 @@ public bool UsePerProcessProxy /// system cache for all instances of Internet Explorer, even those already running /// when the driven instance is launched. Defaults to . /// - public bool EnsureCleanSession - { - get { return this.ensureCleanSession; } - set { this.ensureCleanSession = value; } - } + public bool EnsureCleanSession { get; set; } /// /// Gets or sets a value indicating whether to use the legacy handling for file upload dialogs. /// - public bool LegacyFileUploadDialogHanlding - { - get { return this.legacyFileUploadDialogHandling; } - set { this.legacyFileUploadDialogHandling = value; } - } + public bool LegacyFileUploadDialogHanlding { get; set; } /// /// Gets or sets a value indicating whether to attach to Edge Chrome browser. /// - public bool AttachToEdgeChrome - { - get { return this.attachToEdgeChrome; } - set { this.attachToEdgeChrome = value; } - } + public bool AttachToEdgeChrome { get; set; } /// /// Gets or sets a value indicating whether to ignore process id match with IE Mode on Edge. /// - public bool IgnoreProcessMatch - { - get { return this.ignoreProcessMatch; } - set { this.ignoreProcessMatch = value; } - } + public bool IgnoreProcessMatch { get; set; } /// /// Gets or sets the path to the Edge Browser Executable. /// - public string EdgeExecutablePath - { - get { return this.edgeExecutablePath; } - set { this.edgeExecutablePath = value; } - } + public string? EdgeExecutablePath { get; set; } /// /// Provides a means to add additional capabilities not yet added as type safe options @@ -369,32 +280,32 @@ public override ICapabilities ToCapabilities() private Dictionary BuildInternetExplorerOptionsDictionary() { Dictionary internetExplorerOptionsDictionary = new Dictionary(); - internetExplorerOptionsDictionary[CapabilityType.HasNativeEvents] = this.enableNativeEvents; - internetExplorerOptionsDictionary[EnablePersistentHoverCapability] = this.enablePersistentHover; + internetExplorerOptionsDictionary[CapabilityType.HasNativeEvents] = this.EnableNativeEvents; + internetExplorerOptionsDictionary[EnablePersistentHoverCapability] = this.EnablePersistentHover; - if (this.requireWindowFocus) + if (this.RequireWindowFocus) { internetExplorerOptionsDictionary[RequireWindowFocusCapability] = true; } - if (this.ignoreProtectedModeSettings) + if (this.IntroduceInstabilityByIgnoringProtectedModeSettings) { internetExplorerOptionsDictionary[IgnoreProtectedModeSettingsCapability] = true; } - if (this.ignoreZoomLevel) + if (this.IgnoreZoomLevel) { internetExplorerOptionsDictionary[IgnoreZoomSettingCapability] = true; } - if (!string.IsNullOrEmpty(this.initialBrowserUrl)) + if (!string.IsNullOrEmpty(this.InitialBrowserUrl)) { - internetExplorerOptionsDictionary[InitialBrowserUrlCapability] = this.initialBrowserUrl; + internetExplorerOptionsDictionary[InitialBrowserUrlCapability] = this.InitialBrowserUrl!; } - if (this.elementScrollBehavior != InternetExplorerElementScrollBehavior.Default) + if (this.ElementScrollBehavior != InternetExplorerElementScrollBehavior.Default) { - if (this.elementScrollBehavior == InternetExplorerElementScrollBehavior.Bottom) + if (this.ElementScrollBehavior == InternetExplorerElementScrollBehavior.Bottom) { internetExplorerOptionsDictionary[ElementScrollBehaviorCapability] = 1; } @@ -404,36 +315,36 @@ private Dictionary BuildInternetExplorerOptionsDictionary() } } - if (this.browserAttachTimeout != TimeSpan.MinValue) + if (this.BrowserAttachTimeout != TimeSpan.MinValue) { - internetExplorerOptionsDictionary[BrowserAttachTimeoutCapability] = Convert.ToInt32(this.browserAttachTimeout.TotalMilliseconds); + internetExplorerOptionsDictionary[BrowserAttachTimeoutCapability] = Convert.ToInt32(this.BrowserAttachTimeout.TotalMilliseconds); } - if (this.fileUploadDialogTimeout != TimeSpan.MinValue) + if (this.FileUploadDialogTimeout != TimeSpan.MinValue) { - internetExplorerOptionsDictionary[FileUploadDialogTimeoutCapability] = Convert.ToInt32(this.fileUploadDialogTimeout.TotalMilliseconds); + internetExplorerOptionsDictionary[FileUploadDialogTimeoutCapability] = Convert.ToInt32(this.FileUploadDialogTimeout.TotalMilliseconds); } - if (this.forceCreateProcessApi) + if (this.ForceCreateProcessApi) { internetExplorerOptionsDictionary[ForceCreateProcessApiCapability] = true; - if (!string.IsNullOrEmpty(this.browserCommandLineArguments)) + if (!string.IsNullOrEmpty(this.BrowserCommandLineArguments)) { - internetExplorerOptionsDictionary[BrowserCommandLineSwitchesCapability] = this.browserCommandLineArguments; + internetExplorerOptionsDictionary[BrowserCommandLineSwitchesCapability] = this.BrowserCommandLineArguments!; } } - if (this.forceShellWindowsApi) + if (this.ForceShellWindowsApi) { internetExplorerOptionsDictionary[ForceShellWindowsApiCapability] = true; } if (this.Proxy != null) { - internetExplorerOptionsDictionary[UsePerProcessProxyCapability] = this.usePerProcessProxy; + internetExplorerOptionsDictionary[UsePerProcessProxyCapability] = this.UsePerProcessProxy; } - if (this.ensureCleanSession) + if (this.EnsureCleanSession) { internetExplorerOptionsDictionary[EnsureCleanSessionCapability] = true; } @@ -443,24 +354,24 @@ private Dictionary BuildInternetExplorerOptionsDictionary() internetExplorerOptionsDictionary[EnableFullPageScreenshotCapability] = false; } - if (this.legacyFileUploadDialogHandling) + if (this.LegacyFileUploadDialogHanlding) { internetExplorerOptionsDictionary[LegacyFileUploadDialogHandlingCapability] = true; } - if (this.attachToEdgeChrome) + if (this.AttachToEdgeChrome) { internetExplorerOptionsDictionary[AttachToEdgeChromeCapability] = true; } - if (this.ignoreProcessMatch) + if (this.IgnoreProcessMatch) { internetExplorerOptionsDictionary[IgnoreProcessMatchCapability] = true; } - if (!string.IsNullOrEmpty(this.edgeExecutablePath)) + if (!string.IsNullOrEmpty(this.EdgeExecutablePath)) { - internetExplorerOptionsDictionary[EdgeExecutablePathCapability] = this.edgeExecutablePath; + internetExplorerOptionsDictionary[EdgeExecutablePathCapability] = this.EdgeExecutablePath!; } foreach (KeyValuePair pair in this.additionalInternetExplorerOptions) diff --git a/dotnet/src/webdriver/INetwork.cs b/dotnet/src/webdriver/INetwork.cs index 34e6fa3a6b9f5..ef97172bc45a2 100644 --- a/dotnet/src/webdriver/INetwork.cs +++ b/dotnet/src/webdriver/INetwork.cs @@ -20,6 +20,8 @@ using System; using System.Threading.Tasks; +#nullable enable + namespace OpenQA.Selenium { /// @@ -30,12 +32,12 @@ public interface INetwork /// /// Occurs when a browser sends a network request. /// - event EventHandler NetworkRequestSent; + event EventHandler? NetworkRequestSent; /// /// Occurs when a browser receives a network response. /// - event EventHandler NetworkResponseReceived; + event EventHandler? NetworkResponseReceived; /// /// Adds a to examine incoming network requests, diff --git a/dotnet/src/webdriver/ISupportsLogs.cs b/dotnet/src/webdriver/ISupportsLogs.cs index 1f78ba08f754d..70179010acd61 100644 --- a/dotnet/src/webdriver/ISupportsLogs.cs +++ b/dotnet/src/webdriver/ISupportsLogs.cs @@ -17,6 +17,8 @@ // under the License. // +#nullable enable + namespace OpenQA.Selenium { /// diff --git a/dotnet/src/webdriver/IWrapsElement.cs b/dotnet/src/webdriver/IWrapsElement.cs index 18e91139fd5ec..e4b24ce3572d0 100644 --- a/dotnet/src/webdriver/IWrapsElement.cs +++ b/dotnet/src/webdriver/IWrapsElement.cs @@ -17,6 +17,8 @@ // under the License. // +#nullable enable + namespace OpenQA.Selenium { /// diff --git a/dotnet/src/webdriver/Internal/PortUtilities.cs b/dotnet/src/webdriver/Internal/PortUtilities.cs index 26c45a8b2688f..a48905835dc96 100644 --- a/dotnet/src/webdriver/Internal/PortUtilities.cs +++ b/dotnet/src/webdriver/Internal/PortUtilities.cs @@ -20,6 +20,8 @@ using System.Net; using System.Net.Sockets; +#nullable enable + namespace OpenQA.Selenium.Internal { /// @@ -42,7 +44,7 @@ public static int FindFreePort() { IPEndPoint socketEndPoint = new IPEndPoint(IPAddress.Any, 0); portSocket.Bind(socketEndPoint); - socketEndPoint = (IPEndPoint)portSocket.LocalEndPoint; + socketEndPoint = (IPEndPoint)portSocket.LocalEndPoint!; listeningPort = socketEndPoint.Port; } finally diff --git a/dotnet/src/webdriver/Internal/ReturnedCookie.cs b/dotnet/src/webdriver/Internal/ReturnedCookie.cs index bef50faa49662..bd217fefffdc6 100644 --- a/dotnet/src/webdriver/Internal/ReturnedCookie.cs +++ b/dotnet/src/webdriver/Internal/ReturnedCookie.cs @@ -20,6 +20,8 @@ using System; using System.Globalization; +#nullable enable + namespace OpenQA.Selenium.Internal { /// @@ -42,7 +44,7 @@ public class ReturnedCookie : Cookie /// If the name is or an empty string, /// or if it contains a semi-colon. /// If the value or currentUrl is . - public ReturnedCookie(string name, string value, string domain, string path, DateTime? expiry, bool isSecure, bool isHttpOnly) + public ReturnedCookie(string name, string value, string? domain, string? path, DateTime? expiry, bool isSecure, bool isHttpOnly) : this(name, value, domain, path, expiry, isSecure, isHttpOnly, null) { } @@ -62,7 +64,7 @@ public ReturnedCookie(string name, string value, string domain, string path, Dat /// If the name is or an empty string, /// or if it contains a semi-colon. /// If the value or currentUrl is . - public ReturnedCookie(string name, string value, string domain, string path, DateTime? expiry, bool isSecure, bool isHttpOnly, string sameSite) + public ReturnedCookie(string name, string value, string? domain, string? path, DateTime? expiry, bool isSecure, bool isHttpOnly, string? sameSite) : base(name, value, domain, path, expiry, isSecure, isHttpOnly, sameSite) { diff --git a/dotnet/src/webdriver/LogType.cs b/dotnet/src/webdriver/LogType.cs index 9d87c62638890..e774b65a630d5 100644 --- a/dotnet/src/webdriver/LogType.cs +++ b/dotnet/src/webdriver/LogType.cs @@ -17,6 +17,8 @@ // under the License. // +#nullable enable + namespace OpenQA.Selenium { /// diff --git a/dotnet/src/webdriver/NetworkAuthenticationHandler.cs b/dotnet/src/webdriver/NetworkAuthenticationHandler.cs index a1fa23478f2f1..6ba9bbc44b8bc 100644 --- a/dotnet/src/webdriver/NetworkAuthenticationHandler.cs +++ b/dotnet/src/webdriver/NetworkAuthenticationHandler.cs @@ -18,6 +18,9 @@ // using System; +using System.Diagnostics.CodeAnalysis; + +#nullable enable namespace OpenQA.Selenium { @@ -30,12 +33,14 @@ public class NetworkAuthenticationHandler /// Gets or sets a function that takes a object, and returns a /// value indicating whether the supplied URI matches the specified criteria. /// - public Func UriMatcher { get; set; } + [DisallowNull] + public Func? UriMatcher { get; set; } /// /// Gets or sets the credentials to use when responding to an authentication request /// that matches the specified criteria. /// - public ICredentials Credentials { get; set; } + [DisallowNull] + public ICredentials? Credentials { get; set; } } } diff --git a/dotnet/src/webdriver/NetworkManager.cs b/dotnet/src/webdriver/NetworkManager.cs index 8a215d3721027..6d7f15f63f7eb 100644 --- a/dotnet/src/webdriver/NetworkManager.cs +++ b/dotnet/src/webdriver/NetworkManager.cs @@ -22,6 +22,8 @@ using System.Collections.Generic; using System.Threading.Tasks; +#nullable enable + namespace OpenQA.Selenium { /// @@ -29,10 +31,10 @@ namespace OpenQA.Selenium /// public class NetworkManager : INetwork { - private Lazy session; - private List requestHandlers = new List(); - private List responseHandlers = new List(); - private List authenticationHandlers = new List(); + private readonly Lazy session; + private readonly List requestHandlers = new List(); + private readonly List responseHandlers = new List(); + private readonly List authenticationHandlers = new List(); /// /// Initializes a new instance of the class. @@ -45,8 +47,7 @@ public NetworkManager(IWebDriver driver) // StartMonitoring(). this.session = new Lazy(() => { - IDevTools devToolsDriver = driver as IDevTools; - if (devToolsDriver == null) + if (driver is not IDevTools devToolsDriver) { throw new WebDriverException("Driver must implement IDevTools to use these features"); } @@ -58,12 +59,12 @@ public NetworkManager(IWebDriver driver) /// /// Occurs when a browser sends a network request. /// - public event EventHandler NetworkRequestSent; + public event EventHandler? NetworkRequestSent; /// /// Occurs when a browser receives a network response. /// - public event EventHandler NetworkResponseReceived; + public event EventHandler? NetworkResponseReceived; /// /// Asynchronously starts monitoring for network traffic. @@ -96,6 +97,7 @@ public async Task StopMonitoring() /// and optionally modify the request or provide a response. /// /// The to add. + /// If is . public void AddRequestHandler(NetworkRequestHandler handler) { if (handler == null) @@ -146,8 +148,7 @@ public void AddAuthenticationHandler(NetworkAuthenticationHandler handler) throw new ArgumentException("Credentials to use for authentication cannot be null", nameof(handler)); } - var passwordCredentials = handler.Credentials as PasswordCredentials; - if (passwordCredentials == null) + if (handler.Credentials is not PasswordCredentials) { throw new ArgumentException("Credentials must contain user name and password (PasswordCredentials)", nameof(handler)); } @@ -198,9 +199,9 @@ private async Task OnAuthRequired(object sender, AuthRequiredEventArgs e) bool successfullyAuthenticated = false; foreach (var authenticationHandler in this.authenticationHandlers) { - if (authenticationHandler.UriMatcher.Invoke(uri)) + if (authenticationHandler.UriMatcher!.Invoke(uri)) { - PasswordCredentials credentials = (PasswordCredentials)authenticationHandler.Credentials; + PasswordCredentials credentials = (PasswordCredentials)authenticationHandler.Credentials!; await this.session.Value.Domains.Network.ContinueWithAuth(e.RequestId, credentials.UserName, credentials.Password).ConfigureAwait(false); successfullyAuthenticated = true; break; @@ -222,7 +223,7 @@ private async Task OnRequestPaused(object sender, RequestPausedEventArgs e) foreach (var handler in this.requestHandlers) { - if (handler.RequestMatcher.Invoke(e.RequestData)) + if (handler.RequestMatcher!.Invoke(e.RequestData)) { if (handler.RequestTransformer != null) { @@ -256,14 +257,14 @@ private async Task OnResponsePaused(object sender, ResponsePausedEventArgs e) foreach (var handler in this.responseHandlers) { - if (handler.ResponseMatcher.Invoke(e.ResponseData)) + if (handler.ResponseMatcher!.Invoke(e.ResponseData)) { // NOTE: We create a dummy HttpRequestData object here, because the ContinueRequestWithResponse // method demands one; however, the only property used by that method is the RequestId property. // It might be better to refactor that method signature to simply pass the request ID, or // alternatively, just pass the response data, which should also contain the request ID anyway. HttpRequestData requestData = new HttpRequestData { RequestId = e.ResponseData.RequestId }; - await this.session.Value.Domains.Network.ContinueRequestWithResponse(requestData, handler.ResponseTransformer(e.ResponseData)).ConfigureAwait(false); + await this.session.Value.Domains.Network.ContinueRequestWithResponse(requestData, handler.ResponseTransformer!(e.ResponseData)).ConfigureAwait(false); return; } } diff --git a/dotnet/src/webdriver/NetworkRequestHandler.cs b/dotnet/src/webdriver/NetworkRequestHandler.cs index 93c38d84aaef1..4b2b651d8795f 100644 --- a/dotnet/src/webdriver/NetworkRequestHandler.cs +++ b/dotnet/src/webdriver/NetworkRequestHandler.cs @@ -18,6 +18,9 @@ // using System; +using System.Diagnostics.CodeAnalysis; + +#nullable enable namespace OpenQA.Selenium { @@ -30,20 +33,21 @@ public class NetworkRequestHandler /// Gets or sets a function that evaluates request data in an object, /// and returns a value indicating whether the data matches the specified criteria. /// - public Func RequestMatcher { get; set; } + [DisallowNull] + public Func? RequestMatcher { get; set; } /// /// Gets or sets a function that accepts an object describing a network /// request to be sent, and returns a modified object to use in the actual /// network request. /// - public Func RequestTransformer { get; set; } + public Func? RequestTransformer { get; set; } /// /// Gets or sets a function that accepts an object describing a network /// request to be sent, and returns an object as the response for the /// request, bypassing the actual network request. /// - public Func ResponseSupplier { get; set; } + public Func? ResponseSupplier { get; set; } } } diff --git a/dotnet/src/webdriver/NetworkResponseHandler.cs b/dotnet/src/webdriver/NetworkResponseHandler.cs index e38642f8300e7..27f9e86fbf8e5 100644 --- a/dotnet/src/webdriver/NetworkResponseHandler.cs +++ b/dotnet/src/webdriver/NetworkResponseHandler.cs @@ -18,6 +18,9 @@ // using System; +using System.Diagnostics.CodeAnalysis; + +#nullable enable namespace OpenQA.Selenium { @@ -30,13 +33,15 @@ public class NetworkResponseHandler /// Gets or sets a function that evaluates returned response data in an object, /// and returns a value indicating whether the data matches the specified criteria. /// - public Func ResponseMatcher { get; set; } + [DisallowNull] + public Func? ResponseMatcher { get; set; } /// /// Gets or sets a function that accepts an object describing a network /// response received by the browser, and returns a modified object to used /// as the actual network response. /// - public Func ResponseTransformer { get; set; } + [DisallowNull] + public Func? ResponseTransformer { get; set; } } } diff --git a/dotnet/src/webdriver/RelativeBy.cs b/dotnet/src/webdriver/RelativeBy.cs index 4ed7156a049f9..cdd6bbe1c4a9d 100644 --- a/dotnet/src/webdriver/RelativeBy.cs +++ b/dotnet/src/webdriver/RelativeBy.cs @@ -24,6 +24,8 @@ using System.Globalization; using System.IO; +#nullable enable + namespace OpenQA.Selenium { /// @@ -31,16 +33,16 @@ namespace OpenQA.Selenium /// public class RelativeBy : By { - private string wrappedAtom; - private object root; - private List filters = new List(); + private readonly string wrappedAtom; + private readonly object root; + private readonly List filters = new List(); /// /// Prevents a default instance of the class. /// protected RelativeBy() : base() { - string atom = string.Empty; + string atom; using (Stream atomStream = ResourceUtilities.GetResourceStream("find-elements.js", "find-elements.js")) { using (StreamReader atomReader = new StreamReader(atomStream)) @@ -52,15 +54,11 @@ protected RelativeBy() : base() wrappedAtom = string.Format(CultureInfo.InvariantCulture, "/* findElements */return ({0}).apply(null, arguments);", atom); } - private RelativeBy(object root) : this(root, null) - { - } - - private RelativeBy(object root, List filters) : this() + private RelativeBy(object root, List? filters = null) : this() { - this.root = this.GetSerializableRoot(root); + this.root = GetSerializableRoot(root); - if (filters != null && filters.Count > 0) + if (filters != null) { this.filters.AddRange(filters); } @@ -71,6 +69,7 @@ private RelativeBy(object root, List filters) : this() /// /// A By object that will be used to find the initial element. /// A object to be used in finding the elements. + /// If is null. public static RelativeBy WithLocator(By by) { return new RelativeBy(by); @@ -82,6 +81,7 @@ public static RelativeBy WithLocator(By by) /// /// An object to use to search for the elements. /// The first matching on the current context. + /// If is not or wraps a driver that does. public override IWebElement FindElement(ISearchContext context) { ReadOnlyCollection elements = FindElements(context); @@ -99,12 +99,13 @@ public override IWebElement FindElement(ISearchContext context) /// An object to use to search for the elements. /// A of all WebElements /// matching the current criteria, or an empty list if nothing matches. + /// If is not or wraps a driver that does. public override ReadOnlyCollection FindElements(ISearchContext context) { IJavaScriptExecutor js = GetExecutor(context); Dictionary parameters = new Dictionary(); Dictionary filterParameters = new Dictionary(); - filterParameters["root"] = this.GetSerializableObject(this.root); + filterParameters["root"] = GetSerializableObject(this.root); filterParameters["filters"] = this.filters; parameters["relative"] = filterParameters; object rawElements = js.ExecuteScript(wrappedAtom, parameters); @@ -137,6 +138,7 @@ public override ReadOnlyCollection FindElements(ISearchContext cont /// /// The element to look above for elements. /// A object for use in finding the elements. + /// If is null. public RelativeBy Above(IWebElement element) { if (element == null) @@ -152,6 +154,7 @@ public RelativeBy Above(IWebElement element) /// /// The locator describing the element to look above for elements. /// A object for use in finding the elements. + /// If is null. public RelativeBy Above(By locator) { if (locator == null) @@ -167,6 +170,7 @@ public RelativeBy Above(By locator) /// /// The element to look below for elements. /// A object for use in finding the elements. + /// If is null. public RelativeBy Below(IWebElement element) { if (element == null) @@ -182,6 +186,7 @@ public RelativeBy Below(IWebElement element) /// /// The locator describing the element to look below for elements. /// A object for use in finding the elements. + /// If is null. public RelativeBy Below(By locator) { if (locator == null) @@ -197,6 +202,7 @@ public RelativeBy Below(By locator) /// /// The element to look to the left of for elements. /// A object for use in finding the elements. + /// If is null. public RelativeBy LeftOf(IWebElement element) { if (element == null) @@ -212,6 +218,7 @@ public RelativeBy LeftOf(IWebElement element) /// /// The locator describing the element to look to the left of for elements. /// A object for use in finding the elements. + /// If is null. public RelativeBy LeftOf(By locator) { if (locator == null) @@ -227,6 +234,7 @@ public RelativeBy LeftOf(By locator) /// /// The element to look to the right of for elements. /// A object for use in finding the elements. + /// If is null. public RelativeBy RightOf(IWebElement element) { if (element == null) @@ -242,6 +250,7 @@ public RelativeBy RightOf(IWebElement element) /// /// The locator describing the element to look to the right of for elements. /// A object for use in finding the elements. + /// If is null. public RelativeBy RightOf(By locator) { if (locator == null) @@ -257,6 +266,7 @@ public RelativeBy RightOf(By locator) /// /// The element to look near for elements. /// A object for use in finding the elements. + /// If is null. public RelativeBy Near(IWebElement element) { return Near(element, 50); @@ -268,6 +278,8 @@ public RelativeBy Near(IWebElement element) /// The element to look near for elements. /// The maximum distance from the element to be considered "near." /// A object for use in finding the elements. + /// If is null. + /// If is not a positive value. public RelativeBy Near(IWebElement element, int atMostDistanceInPixels) { return Near((object)element, atMostDistanceInPixels); @@ -278,6 +290,7 @@ public RelativeBy Near(IWebElement element, int atMostDistanceInPixels) /// /// The locator describing the element to look near for elements. /// A object for use in finding the elements. + /// If is null. public RelativeBy Near(By locator) { return Near(locator, 50); @@ -289,6 +302,8 @@ public RelativeBy Near(By locator) /// The locator describing the element to look near for elements. /// The maximum distance from the element to be considered "near." /// A object for use in finding the elements. + /// If is null. + /// If is not a positive value. public RelativeBy Near(By locator, int atMostDistanceInPixels) { return Near((object)locator, atMostDistanceInPixels); @@ -303,7 +318,7 @@ private RelativeBy Near(object locator, int atMostDistanceInPixels) if (atMostDistanceInPixels <= 0) { - throw new ArgumentException("Distance must be greater than zero", nameof(atMostDistanceInPixels)); + throw new ArgumentOutOfRangeException(nameof(atMostDistanceInPixels), "Distance must be greater than zero"); } Dictionary filter = new Dictionary(); @@ -334,27 +349,24 @@ private RelativeBy SimpleDirection(string direction, object locator) return new RelativeBy(this.root, this.filters); } - private object GetSerializableRoot(object toSerialize) + private static object GetSerializableRoot(object root) { - if (toSerialize == null) + if (root == null) { - throw new ArgumentNullException(nameof(toSerialize), "object to serialize must not be null"); + throw new ArgumentNullException(nameof(root), "object to serialize must not be null"); } - By asBy = toSerialize as By; - if (asBy != null) + if (root is By asBy) { return asBy; } - IWebElement element = toSerialize as IWebElement; - if (element != null) + if (root is IWebElement element) { return element; } - IWrapsElement wrapper = toSerialize as IWrapsElement; - if (wrapper != null) + if (root is IWrapsElement wrapper) { return wrapper.WrappedElement; } @@ -362,29 +374,26 @@ private object GetSerializableRoot(object toSerialize) throw new WebDriverException("Serializable locator must be a By, an IWebElement, or a wrapped element using IWrapsElement"); } - private object GetSerializableObject(object toSerialize) + private static object GetSerializableObject(object root) { - if (toSerialize == null) + if (root == null) { - throw new ArgumentNullException(nameof(toSerialize), "object to serialize must not be null"); + throw new ArgumentNullException(nameof(root), "object to serialize must not be null"); } - By asBy = toSerialize as By; - if (asBy != null) + if (root is By asBy) { Dictionary serializedBy = new Dictionary(); serializedBy[asBy.Mechanism] = asBy.Criteria; return serializedBy; } - IWebElement element = toSerialize as IWebElement; - if (element != null) + if (root is IWebElement element) { return element; } - IWrapsElement wrapper = toSerialize as IWrapsElement; - if (wrapper != null) + if (root is IWrapsElement wrapper) { return wrapper.WrappedElement; } @@ -392,15 +401,15 @@ private object GetSerializableObject(object toSerialize) throw new WebDriverException("Serializable locator must be a By, an IWebElement, or a wrapped element using IWrapsElement"); } - private IJavaScriptExecutor GetExecutor(ISearchContext context) + private static IJavaScriptExecutor GetExecutor(ISearchContext context) { - IJavaScriptExecutor executor = context as IJavaScriptExecutor; + IJavaScriptExecutor? executor = context as IJavaScriptExecutor; if (executor != null) { return executor; } - IWrapsDriver current = context as IWrapsDriver; + IWrapsDriver? current = context as IWrapsDriver; while (current != null) { IWebDriver driver = current.WrappedDriver; @@ -415,7 +424,7 @@ private IJavaScriptExecutor GetExecutor(ISearchContext context) if (executor == null) { - throw new ArgumentException("Search context must support JavaScript or IWrapsDriver where the wrappted driver supports JavaScript", nameof(context)); + throw new ArgumentException("Search context must support JavaScript or IWrapsDriver where the wrapped driver supports JavaScript", nameof(context)); } return executor; diff --git a/dotnet/src/webdriver/Remote/DesiredCapabilities.cs b/dotnet/src/webdriver/Remote/DesiredCapabilities.cs index 7784f2395fc3e..7e3301ad9b374 100644 --- a/dotnet/src/webdriver/Remote/DesiredCapabilities.cs +++ b/dotnet/src/webdriver/Remote/DesiredCapabilities.cs @@ -23,6 +23,8 @@ using System.Collections.ObjectModel; using System.Globalization; +#nullable enable + namespace OpenQA.Selenium.Remote { /// @@ -65,25 +67,22 @@ public DesiredCapabilities(Dictionary rawMap) { if (rawMap != null) { - foreach (string key in rawMap.Keys) + foreach (KeyValuePair entry in rawMap) { - if (key == CapabilityType.Platform) + if (entry.Key == CapabilityType.Platform) { - object raw = rawMap[CapabilityType.Platform]; - string rawAsString = raw as string; - Platform rawAsPlatform = raw as Platform; - if (rawAsString != null) + if (entry.Value is string rawAsString) { this.SetCapability(CapabilityType.Platform, Platform.FromString(rawAsString)); } - else if (rawAsPlatform != null) + else if (entry.Value is Platform rawAsPlatform) { this.SetCapability(CapabilityType.Platform, rawAsPlatform); } } else { - this.SetCapability(key, rawMap[key]); + this.SetCapability(entry.Key, entry.Value); } } } @@ -107,54 +106,21 @@ internal DesiredCapabilities(string browser, string version, Platform platform, /// /// Gets the browser name /// - public string BrowserName - { - get - { - string name = string.Empty; - object capabilityValue = this.GetCapability(CapabilityType.BrowserName); - if (capabilityValue != null) - { - name = capabilityValue.ToString(); - } - - return name; - } - } + public string BrowserName => this.GetCapability(CapabilityType.BrowserName)?.ToString() ?? string.Empty; /// /// Gets or sets the platform /// public Platform Platform { - get - { - return this.GetCapability(CapabilityType.Platform) as Platform ?? new Platform(PlatformType.Any); - } - - set - { - this.SetCapability(CapabilityType.Platform, value); - } + get => this.GetCapability(CapabilityType.Platform) as Platform ?? new Platform(PlatformType.Any); + set => this.SetCapability(CapabilityType.Platform, value); } /// /// Gets the browser version /// - public string Version - { - get - { - string browserVersion = string.Empty; - object capabilityValue = this.GetCapability(CapabilityType.Version); - if (capabilityValue != null) - { - browserVersion = capabilityValue.ToString(); - } - - return browserVersion; - } - } + public string Version => this.GetCapability(CapabilityType.Version)?.ToString() ?? string.Empty; /// /// Gets or sets a value indicating whether the browser accepts SSL certificates. @@ -164,7 +130,7 @@ public bool AcceptInsecureCerts get { bool acceptSSLCerts = false; - object capabilityValue = this.GetCapability(CapabilityType.AcceptInsecureCertificates); + object? capabilityValue = this.GetCapability(CapabilityType.AcceptInsecureCertificates); if (capabilityValue != null) { acceptSSLCerts = (bool)capabilityValue; @@ -173,27 +139,18 @@ public bool AcceptInsecureCerts return acceptSSLCerts; } - set - { - this.SetCapability(CapabilityType.AcceptInsecureCertificates, value); - } + set => this.SetCapability(CapabilityType.AcceptInsecureCertificates, value); } /// /// Gets the underlying Dictionary for a given set of capabilities. /// - IDictionary IHasCapabilitiesDictionary.CapabilitiesDictionary - { - get { return this.CapabilitiesDictionary; } - } + IDictionary IHasCapabilitiesDictionary.CapabilitiesDictionary => this.CapabilitiesDictionary; /// /// Gets the underlying Dictionary for a given set of capabilities. /// - internal IDictionary CapabilitiesDictionary - { - get { return new ReadOnlyDictionary(this.capabilities); } - } + internal IDictionary CapabilitiesDictionary => new ReadOnlyDictionary(this.capabilities); /// /// Gets the capability value with the specified name. @@ -207,12 +164,12 @@ public object this[string capabilityName] { get { - if (!this.capabilities.ContainsKey(capabilityName)) + if (!this.capabilities.TryGetValue(capabilityName, out object? capabilityValue)) { throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, "The capability {0} is not present in this set of capabilities", capabilityName)); } - return this.capabilities[capabilityName]; + return capabilityValue; } } @@ -232,16 +189,15 @@ public bool HasCapability(string capability) /// The capability to get. /// An object associated with the capability, or /// if the capability is not set on the browser. - public object GetCapability(string capability) + public object? GetCapability(string capability) { - object capabilityValue = null; - if (this.capabilities.ContainsKey(capability)) + object? capabilityValue = null; + if (this.capabilities.TryGetValue(capability, out object? value)) { - capabilityValue = this.capabilities[capability]; - string capabilityValueString = capabilityValue as string; - if (capability == CapabilityType.Platform && capabilityValueString != null) + capabilityValue = value; + if (capability == CapabilityType.Platform && capabilityValue is string capabilityValueString) { - capabilityValue = Platform.FromString(capabilityValue.ToString()); + capabilityValue = Platform.FromString(capabilityValueString); } } @@ -258,8 +214,7 @@ public void SetCapability(string capability, object capabilityValue) // Handle the special case of Platform objects. These should // be stored in the underlying dictionary as their protocol // string representation. - Platform platformCapabilityValue = capabilityValue as Platform; - if (platformCapabilityValue != null) + if (capabilityValue is Platform platformCapabilityValue) { this.capabilities[capability] = platformCapabilityValue.ProtocolPlatformType; } @@ -296,15 +251,14 @@ public override string ToString() /// /// DesiredCapabilities you wish to compare /// true if they are the same or false if they are not - public override bool Equals(object obj) + public override bool Equals(object? obj) { if (this == obj) { return true; } - DesiredCapabilities other = obj as DesiredCapabilities; - if (other == null) + if (obj is not DesiredCapabilities other) { return false; } @@ -333,8 +287,7 @@ public override bool Equals(object obj) /// A read-only version of this capabilities object. public ICapabilities AsReadOnly() { - ReadOnlyDesiredCapabilities readOnlyCapabilities = new ReadOnlyDesiredCapabilities(this); - return readOnlyCapabilities; + return new ReadOnlyDesiredCapabilities(this); } } } diff --git a/dotnet/src/webdriver/Remote/RemoteWebDriver.cs b/dotnet/src/webdriver/Remote/RemoteWebDriver.cs index 0950c84949ff9..cc63a718583b4 100644 --- a/dotnet/src/webdriver/Remote/RemoteWebDriver.cs +++ b/dotnet/src/webdriver/Remote/RemoteWebDriver.cs @@ -26,6 +26,9 @@ using System.IO.Compression; using System.Linq; using System.Threading.Tasks; +using System.Diagnostics.CodeAnalysis; + +#nullable enable namespace OpenQA.Selenium.Remote { @@ -78,7 +81,7 @@ public class RemoteWebDriver : WebDriver, IDevTools, IHasDownloads private const string DefaultRemoteServerUrl = "http://127.0.0.1:4444/wd/hub"; - private DevToolsSession devToolsSession; + private DevToolsSession? devToolsSession; /// /// Initializes a new instance of the class. This constructor defaults proxy to http://127.0.0.1:4444/wd/hub @@ -142,10 +145,8 @@ public RemoteWebDriver(ICommandExecutor commandExecutor, ICapabilities capabilit /// /// Gets a value indicating whether a DevTools session is active. /// - public bool HasActiveDevToolsSession - { - get { return this.devToolsSession != null; } - } + [MemberNotNullWhen(true, nameof(devToolsSession))] + public bool HasActiveDevToolsSession => this.devToolsSession != null; /// /// Finds the first element in the page that matches the ID supplied @@ -208,7 +209,7 @@ public IWebElement FindElementByClassName(string className) // Finding elements by class name with whitespace is not allowed. // However, converting the single class name to a valid CSS selector // by prepending a '.' may result in a still-valid, but incorrect - // selector. Thus, we short-ciruit that behavior here. + // selector. Thus, we short-circuit that behavior here. throw new InvalidSelectorException("Compound class names not allowed. Cannot have whitespace in class name. Use CSS selectors instead."); } @@ -234,7 +235,7 @@ public ReadOnlyCollection FindElementsByClassName(string className) // Finding elements by class name with whitespace is not allowed. // However, converting the single class name to a valid CSS selector // by prepending a '.' may result in a still-valid, but incorrect - // selector. Thus, we short-ciruit that behavior here. + // selector. Thus, we short-circuit that behavior here. throw new InvalidSelectorException("Compound class names not allowed. Cannot have whitespace in class name. Use CSS selectors instead."); } @@ -452,24 +453,25 @@ public DevToolsSession GetDevToolsSession(DevToolsOptions options) if (this.devToolsSession == null) { - if (!this.Capabilities.HasCapability(RemoteDevToolsEndPointCapabilityName)) + object? debuggerAddressObject = this.Capabilities.GetCapability(RemoteDevToolsEndPointCapabilityName); + if (debuggerAddressObject is null) { throw new WebDriverException("Cannot find " + RemoteDevToolsEndPointCapabilityName + " capability for driver"); } - string debuggerAddress = this.Capabilities.GetCapability(RemoteDevToolsEndPointCapabilityName).ToString(); + string debuggerAddress = debuggerAddressObject.ToString()!; if (!options.ProtocolVersion.HasValue || options.ProtocolVersion == DevToolsSession.AutoDetectDevToolsProtocolVersion) { - if (!this.Capabilities.HasCapability(RemoteDevToolsVersionCapabilityName)) + object? versionObject = this.Capabilities.GetCapability(RemoteDevToolsVersionCapabilityName); + if (versionObject is null) { throw new WebDriverException("Cannot find " + RemoteDevToolsVersionCapabilityName + " capability for driver"); } - string version = this.Capabilities.GetCapability(RemoteDevToolsVersionCapabilityName).ToString(); + string version = versionObject.ToString()!; - bool versionParsed = int.TryParse(version.Substring(0, version.IndexOf(".")), out int devToolsProtocolVersion); - if (!versionParsed) + if (!int.TryParse(version.Substring(0, version.IndexOf(".")), out int devToolsProtocolVersion)) { throw new WebDriverException("Cannot parse protocol version from reported version string: " + version); } @@ -516,9 +518,14 @@ public IReadOnlyList GetDownloadableFiles() } Response commandResponse = this.Execute(DriverCommand.GetDownloadableFiles, null); - Dictionary value = (Dictionary)commandResponse.Value; - object[] namesArray = (object[])value["names"]; - return namesArray.Select(obj => obj.ToString()).ToList(); + + if (commandResponse.Value is not Dictionary value) + { + throw new WebDriverException("GetDownloadableFiles returned successfully, but response content was not an object: " + commandResponse.Value); + } + + object[] namesArray = (object[])value["names"]!; + return namesArray.Select(obj => obj.ToString()!).ToList(); } /// @@ -540,7 +547,12 @@ public void DownloadFile(string fileName, string targetDirectory) }; Response commandResponse = this.Execute(DriverCommand.DownloadFile, parameters); - string contents = ((Dictionary)commandResponse.Value)["contents"].ToString(); + if (commandResponse.Value is not Dictionary value) + { + throw new WebDriverException("DownloadFile returned successfully, but response content was not an object: " + commandResponse.Value); + } + + string contents = value["contents"]!.ToString()!; byte[] fileData = Convert.FromBase64String(contents); Directory.CreateDirectory(targetDirectory);