From 60e1f96849abd65660c7cf2ffb46bc7d96cbe4e4 Mon Sep 17 00:00:00 2001 From: Michael Render Date: Mon, 3 Feb 2025 12:13:22 -0500 Subject: [PATCH] [dotnet] Handle nullability on `WebElement` --- dotnet/src/webdriver/ILocatable.cs | 2 + dotnet/src/webdriver/ITakesScreenshot.cs | 2 + dotnet/src/webdriver/WebElement.cs | 171 +++++++++------------- dotnet/src/webdriver/WebElementFactory.cs | 52 +++---- 4 files changed, 97 insertions(+), 130 deletions(-) diff --git a/dotnet/src/webdriver/ILocatable.cs b/dotnet/src/webdriver/ILocatable.cs index eeb0bfa0ef475..91bd16c38a755 100644 --- a/dotnet/src/webdriver/ILocatable.cs +++ b/dotnet/src/webdriver/ILocatable.cs @@ -20,6 +20,8 @@ using OpenQA.Selenium.Interactions.Internal; using System.Drawing; +#nullable enable + namespace OpenQA.Selenium { /// diff --git a/dotnet/src/webdriver/ITakesScreenshot.cs b/dotnet/src/webdriver/ITakesScreenshot.cs index f2b7de679de5d..6583e2809db09 100644 --- a/dotnet/src/webdriver/ITakesScreenshot.cs +++ b/dotnet/src/webdriver/ITakesScreenshot.cs @@ -17,6 +17,8 @@ // under the License. // +#nullable enable + namespace OpenQA.Selenium { /// diff --git a/dotnet/src/webdriver/WebElement.cs b/dotnet/src/webdriver/WebElement.cs index 3d4a81338d61f..0c0eca538b109 100644 --- a/dotnet/src/webdriver/WebElement.cs +++ b/dotnet/src/webdriver/WebElement.cs @@ -40,28 +40,24 @@ public class WebElement : IWebElement, IFindsElement, IWrapsDriver, ILocatable, /// public const string ElementReferencePropertyName = "element-6066-11e4-a52e-4f735466cecf"; - private WebDriver driver; - private string elementId; + private readonly WebDriver driver; /// /// Initializes a new instance of the class. /// /// The instance that is driving this element. /// The ID value provided to identify the element. - /// If is . + /// If or are . public WebElement(WebDriver parentDriver, string id) { - this.driver = parentDriver; - this.elementId = id ?? throw new ArgumentNullException(nameof(id)); + this.driver = parentDriver ?? throw new ArgumentNullException(nameof(parentDriver)); + this.Id = id ?? throw new ArgumentNullException(nameof(id)); } /// /// Gets the driving this element. /// - public IWebDriver WrappedDriver - { - get { return this.driver; } - } + public IWebDriver WrappedDriver => this.driver; /// /// Gets the tag name of this element. @@ -77,8 +73,10 @@ public virtual string TagName get { Dictionary parameters = new Dictionary(); - parameters.Add("id", this.elementId); + parameters.Add("id", this.Id); + Response commandResponse = this.Execute(DriverCommand.GetElementTagName, parameters); + return commandResponse.Value.ToString(); } } @@ -93,8 +91,10 @@ public virtual string Text get { Dictionary parameters = new Dictionary(); - parameters.Add("id", this.elementId); + parameters.Add("id", this.Id); + Response commandResponse = this.Execute(DriverCommand.GetElementText, parameters); + return commandResponse.Value.ToString(); } } @@ -110,9 +110,11 @@ public virtual bool Enabled get { Dictionary parameters = new Dictionary(); - parameters.Add("id", this.elementId); + parameters.Add("id", this.Id); + Response commandResponse = this.Execute(DriverCommand.IsElementEnabled, parameters); - return (bool)Convert.ChangeType(commandResponse.Value, typeof(bool)); + + return Convert.ToBoolean(commandResponse.Value); } } @@ -127,9 +129,11 @@ public virtual bool Selected get { Dictionary parameters = new Dictionary(); - parameters.Add("id", this.elementId); + parameters.Add("id", this.Id); + Response commandResponse = this.Execute(DriverCommand.IsElementSelected, parameters); - return (bool)Convert.ChangeType(commandResponse.Value, typeof(bool)); + + return Convert.ToBoolean(commandResponse.Value); } } @@ -142,10 +146,11 @@ public virtual Point Location { get { - string getLocationCommand = DriverCommand.GetElementRect; Dictionary parameters = new Dictionary(); parameters.Add("id", this.Id); - Response commandResponse = this.Execute(getLocationCommand, parameters); + + Response commandResponse = this.Execute(DriverCommand.GetElementRect, parameters); + Dictionary rawPoint = (Dictionary)commandResponse.Value; int x = Convert.ToInt32(rawPoint["x"], CultureInfo.InvariantCulture); int y = Convert.ToInt32(rawPoint["y"], CultureInfo.InvariantCulture); @@ -161,10 +166,11 @@ public virtual Size Size { get { - string getSizeCommand = DriverCommand.GetElementRect; Dictionary parameters = new Dictionary(); parameters.Add("id", this.Id); - Response commandResponse = this.Execute(getSizeCommand, parameters); + + Response commandResponse = this.Execute(DriverCommand.GetElementRect, parameters); + Dictionary rawSize = (Dictionary)commandResponse.Value; int width = Convert.ToInt32(rawSize["width"], CultureInfo.InvariantCulture); int height = Convert.ToInt32(rawSize["height"], CultureInfo.InvariantCulture); @@ -183,14 +189,14 @@ public virtual bool Displayed { get { - Response commandResponse = null; Dictionary parameters = new Dictionary(); string atom = GetAtom("is-displayed.js"); parameters.Add("script", atom); - parameters.Add("args", new object[] { this.ToElementReference().ToDictionary() }); - commandResponse = this.Execute(DriverCommand.ExecuteScript, parameters); + parameters.Add("args", new object[] { ((IWebDriverObjectReference)this).ToDictionary() }); - return (bool)Convert.ChangeType(commandResponse.Value, typeof(bool)); + Response commandResponse = Execute(DriverCommand.ExecuteScript, parameters); + + return Convert.ToBoolean(commandResponse.Value); } } @@ -201,9 +207,9 @@ public virtual Point LocationOnScreenOnceScrolledIntoView { get { - Dictionary rawLocation; object scriptResponse = this.driver.ExecuteScript("var rect = arguments[0].getBoundingClientRect(); return {'x': rect.left, 'y': rect.top};", this); - rawLocation = scriptResponse as Dictionary; + + Dictionary rawLocation = (Dictionary)scriptResponse; int x = Convert.ToInt32(rawLocation["x"], CultureInfo.InvariantCulture); int y = Convert.ToInt32(rawLocation["y"], CultureInfo.InvariantCulture); @@ -220,7 +226,9 @@ public virtual string ComputedAccessibleLabel { Dictionary parameters = new Dictionary(); parameters.Add("id", this.Id); + Response commandResponse = this.Execute(DriverCommand.GetComputedAccessibleLabel, parameters); + return commandResponse.Value.ToString(); } } @@ -233,12 +241,14 @@ public virtual string ComputedAccessibleRole get { // TODO: Returning this as a string is incorrect. The W3C WebDriver Specification - // needs to be updated to more throughly document the structure of what is returned + // needs to be updated to more thoroughly document the structure of what is returned // by this command. Once that is done, a type-safe class will be created, and will // be returned by this property. Dictionary parameters = new Dictionary(); parameters.Add("id", this.Id); + Response commandResponse = this.Execute(DriverCommand.GetComputedAccessibleRole, parameters); + return commandResponse.Value.ToString(); } } @@ -247,18 +257,12 @@ public virtual string ComputedAccessibleRole /// Gets the coordinates identifying the location of this element using /// various frames of reference. /// - public virtual ICoordinates Coordinates - { - get { return new ElementCoordinates(this); } - } + public virtual ICoordinates Coordinates => new ElementCoordinates(this); /// /// Gets the internal ID of the element. /// - string IWebDriverObjectReference.ObjectReferenceId - { - get { return this.elementId; } - } + string IWebDriverObjectReference.ObjectReferenceId => this.Id; /// /// Gets the ID of the element @@ -270,10 +274,7 @@ string IWebDriverObjectReference.ObjectReferenceId /// and the parent driver hosting the element have a need to access the /// internal element ID. Therefore, we have two properties returning the /// same value, one scoped as internal, the other as protected. - protected string Id - { - get { return this.elementId; } - } + protected string Id { get; } /// /// Clears the content of this element. @@ -285,7 +286,8 @@ protected string Id public virtual void Clear() { Dictionary parameters = new Dictionary(); - parameters.Add("id", this.elementId); + parameters.Add("id", this.Id); + this.Execute(DriverCommand.ClearElement, parameters); } @@ -306,7 +308,8 @@ public virtual void Clear() public virtual void Click() { Dictionary parameters = new Dictionary(); - parameters.Add("id", this.elementId); + parameters.Add("id", this.Id); + this.Execute(DriverCommand.ClickElement, parameters); } @@ -335,10 +338,12 @@ public virtual IWebElement FindElement(By by) public virtual IWebElement FindElement(string mechanism, string value) { Dictionary parameters = new Dictionary(); - parameters.Add("id", this.elementId); + parameters.Add("id", this.Id); parameters.Add("using", mechanism); parameters.Add("value", value); + Response commandResponse = this.Execute(DriverCommand.FindChildElement, parameters); + return this.driver.GetElementFromResponse(commandResponse); } @@ -368,10 +373,12 @@ public virtual ReadOnlyCollection FindElements(By by) public virtual ReadOnlyCollection FindElements(string mechanism, string value) { Dictionary parameters = new Dictionary(); - parameters.Add("id", this.elementId); + parameters.Add("id", this.Id); parameters.Add("using", mechanism); parameters.Add("value", value); + Response commandResponse = this.Execute(DriverCommand.FindChildElements, parameters); + return this.driver.GetElementsFromResponse(commandResponse); } @@ -419,25 +426,17 @@ public virtual string GetAttribute(string attributeName) Dictionary parameters = new Dictionary(); string atom = GetAtom("get-attribute.js"); parameters.Add("script", atom); - parameters.Add("args", new object[] { this.ToElementReference().ToDictionary(), attributeName }); + parameters.Add("args", new object[] { ((IWebDriverObjectReference)this).ToDictionary(), attributeName }); commandResponse = this.Execute(DriverCommand.ExecuteScript, parameters); - if (commandResponse.Value == null) - { - attributeValue = null; - } - else - { - attributeValue = commandResponse.Value.ToString(); - // Normalize string values of boolean results as lowercase. - if (commandResponse.Value is bool) - { - attributeValue = attributeValue.ToLowerInvariant(); - } + // Normalize string values of boolean results as lowercase. + if (commandResponse.Value is bool b) + { + return b ? "true" : "false"; } - return attributeValue; + return commandResponse.Value?.ToString(); } /// @@ -455,22 +454,13 @@ public virtual string GetAttribute(string attributeName) /// public virtual string GetDomAttribute(string attributeName) { - string attributeValue = string.Empty; Dictionary parameters = new Dictionary(); parameters.Add("id", this.Id); parameters.Add("name", attributeName); Response commandResponse = this.Execute(DriverCommand.GetElementAttribute, parameters); - if (commandResponse.Value == null) - { - attributeValue = null; - } - else - { - attributeValue = commandResponse.Value.ToString(); - } - return attributeValue; + return commandResponse.Value?.ToString(); } /// @@ -482,22 +472,13 @@ public virtual string GetDomAttribute(string attributeName) /// Thrown when the target element is no longer valid in the document DOM. public virtual string GetDomProperty(string propertyName) { - string propertyValue = string.Empty; Dictionary parameters = new Dictionary(); parameters.Add("id", this.Id); parameters.Add("name", propertyName); Response commandResponse = this.Execute(DriverCommand.GetElementProperty, parameters); - if (commandResponse.Value == null) - { - propertyValue = null; - } - else - { - propertyValue = commandResponse.Value.ToString(); - } - return propertyValue; + return commandResponse.Value?.ToString(); } /// @@ -512,19 +493,17 @@ public virtual ISearchContext GetShadowRoot() parameters.Add("id", this.Id); Response commandResponse = this.Execute(DriverCommand.GetElementShadowRoot, parameters); - Dictionary shadowRootDictionary = commandResponse.Value as Dictionary; - if (shadowRootDictionary == null) + if (commandResponse.Value is not Dictionary shadowRootDictionary) { throw new WebDriverException("Get shadow root command succeeded, but response value does not represent a shadow root."); } - if (!shadowRootDictionary.ContainsKey(ShadowRoot.ShadowRootReferencePropertyName)) + if (!ShadowRoot.TryCreate(this.driver, shadowRootDictionary, out ShadowRoot shadowRoot)) { throw new WebDriverException("Get shadow root command succeeded, but response value does not have a shadow root key value."); } - string shadowRootId = shadowRootDictionary[ShadowRoot.ShadowRootReferencePropertyName].ToString(); - return new ShadowRoot(this.driver, shadowRootId); + return shadowRoot; } /// @@ -555,7 +534,7 @@ public virtual string GetCssValue(string propertyName) public virtual Screenshot GetScreenshot() { Dictionary parameters = new Dictionary(); - parameters.Add("id", this.elementId); + parameters.Add("id", this.Id); // Get the screenshot as base64. Response screenshotResponse = this.Execute(DriverCommand.ElementScreenshot, parameters); @@ -601,7 +580,7 @@ public virtual void SendKeys(string text) // TODO: Remove either "keysToSend" or "value" property, whichever is not the // appropriate one for spec compliance. Dictionary parameters = new Dictionary(); - parameters.Add("id", this.elementId); + parameters.Add("id", this.Id); parameters.Add("text", text); parameters.Add("value", text.ToCharArray()); @@ -625,7 +604,7 @@ public virtual void Submit() } else { - String script = "/* submitForm */var form = arguments[0];\n" + + string script = "/* submitForm */var form = arguments[0];\n" + "while (form.nodeName != \"FORM\" && form.parentNode) {\n" + " form = form.parentNode;\n" + "}\n" + @@ -645,7 +624,7 @@ public virtual void Submit() /// A string that represents the current . public override string ToString() { - return string.Format(CultureInfo.InvariantCulture, "Element (id = {0})", this.elementId); + return string.Format(CultureInfo.InvariantCulture, "Element (id = {0})", this.Id); } /// @@ -654,7 +633,7 @@ public override string ToString() /// Integer of the hash code for the element public override int GetHashCode() { - return this.elementId.GetHashCode(); + return this.Id.GetHashCode(); } /// @@ -664,25 +643,22 @@ public override int GetHashCode() /// A boolean if it is equal or not public override bool Equals(object obj) { - IWebElement other = obj as IWebElement; - if (other == null) + if (obj is not IWebElement other) { return false; } - IWrapsElement objAsWrapsElement = obj as IWrapsElement; - if (objAsWrapsElement != null) + if (obj is IWrapsElement objAsWrapsElement) { other = objAsWrapsElement.WrappedElement; } - WebElement otherAsElement = other as WebElement; - if (otherAsElement == null) + if (other is not WebElement otherAsElement) { return false; } - if (this.elementId == otherAsElement.Id) + if (this.Id == otherAsElement.Id) { // For drivers that implement ID equality, we can check for equal IDs // here, and expect them to be equal. There is a potential danger here @@ -696,7 +672,7 @@ public override bool Equals(object obj) Dictionary IWebDriverObjectReference.ToDictionary() { Dictionary elementDictionary = new Dictionary(); - elementDictionary.Add(ElementReferencePropertyName, this.elementId); + elementDictionary.Add(ElementReferencePropertyName, this.Id); return elementDictionary; } @@ -729,7 +705,7 @@ private static string GetAtom(string atomResourceName) private string UploadFile(string localFile) { - string base64zip = string.Empty; + string base64zip; try { using (MemoryStream fileUploadMemoryStream = new MemoryStream()) @@ -752,10 +728,5 @@ private string UploadFile(string localFile) throw new WebDriverException("Cannot upload " + localFile, e); } } - - private IWebDriverObjectReference ToElementReference() - { - return this as IWebDriverObjectReference; - } } } diff --git a/dotnet/src/webdriver/WebElementFactory.cs b/dotnet/src/webdriver/WebElementFactory.cs index 31629129d0e8d..eb6428354e121 100644 --- a/dotnet/src/webdriver/WebElementFactory.cs +++ b/dotnet/src/webdriver/WebElementFactory.cs @@ -20,6 +20,8 @@ using System; using System.Collections.Generic; +#nullable enable + namespace OpenQA.Selenium { /// @@ -27,24 +29,20 @@ namespace OpenQA.Selenium /// public class WebElementFactory { - private WebDriver driver; - /// /// Initializes a new instance of the class. /// /// The object used to locate the elements. + /// If is . public WebElementFactory(WebDriver parentDriver) { - this.driver = parentDriver; + this.ParentDriver = parentDriver ?? throw new ArgumentNullException(nameof(parentDriver)); } /// /// Gets the instance used to locate elements. /// - protected WebDriver ParentDriver - { - get { return this.driver; } - } + protected WebDriver ParentDriver { get; } /// /// Creates a from a dictionary containing a reference to an element. @@ -58,14 +56,18 @@ public virtual WebElement CreateElement(Dictionary elementDictio } /// - /// Gets a value indicating wether the specified dictionary represents a reference to a web element. + /// Gets a value indicating whether the specified dictionary represents a reference to a web element. /// /// The dictionary to check. /// if the dictionary contains an element reference; otherwise, . public bool ContainsElementReference(Dictionary elementDictionary) { - string elementPropertyName; - return this.TryGetElementPropertyName(elementDictionary, out elementPropertyName); + if (elementDictionary == null) + { + throw new ArgumentNullException(nameof(elementDictionary), "The dictionary containing the element reference cannot be null"); + } + + return elementDictionary.ContainsKey(WebElement.ElementReferencePropertyName); } /// @@ -73,15 +75,22 @@ public bool ContainsElementReference(Dictionary elementDictionar /// /// A dictionary containing the element reference. /// The internal ID associated with the element. + /// If is . + /// If the dictionary does not contain the element reference property name. + /// If the element property is or . public string GetElementId(Dictionary elementDictionary) { - string elementPropertyName; - if (!this.TryGetElementPropertyName(elementDictionary, out elementPropertyName)) + if (elementDictionary == null) + { + throw new ArgumentNullException(nameof(elementDictionary), "The dictionary containing the element reference cannot be null"); + } + + if (!elementDictionary.TryGetValue(WebElement.ElementReferencePropertyName, out object? elementIdObj)) { throw new ArgumentException("elementDictionary", "The specified dictionary does not contain an element reference"); } - string elementId = elementDictionary[elementPropertyName].ToString(); + string? elementId = elementIdObj.ToString(); if (string.IsNullOrEmpty(elementId)) { throw new InvalidOperationException("The specified element ID is either null or the empty string."); @@ -89,22 +98,5 @@ public string GetElementId(Dictionary elementDictionary) return elementId; } - - private bool TryGetElementPropertyName(Dictionary elementDictionary, out string elementPropertyName) - { - if (elementDictionary == null) - { - throw new ArgumentNullException(nameof(elementDictionary), "The dictionary containing the element reference cannot be null"); - } - - if (elementDictionary.ContainsKey(WebElement.ElementReferencePropertyName)) - { - elementPropertyName = WebElement.ElementReferencePropertyName; - return true; - } - - elementPropertyName = string.Empty; - return false; - } } }