From 1d4a57b7403f938c2a9309a19fc098e4ade0fd98 Mon Sep 17 00:00:00 2001 From: Michael Render Date: Sat, 1 Feb 2025 01:14:44 -0500 Subject: [PATCH 1/2] [dotnet] Annotate nullability on `Actions` type --- dotnet/src/webdriver/Interactions/Actions.cs | 74 ++++++++++++------- .../src/webdriver/Interactions/InputDevice.cs | 1 + .../Interactions/PauseInteraction.cs | 1 + 3 files changed, 50 insertions(+), 26 deletions(-) diff --git a/dotnet/src/webdriver/Interactions/Actions.cs b/dotnet/src/webdriver/Interactions/Actions.cs index e3672dd5a5c7a..13eb5481b5728 100644 --- a/dotnet/src/webdriver/Interactions/Actions.cs +++ b/dotnet/src/webdriver/Interactions/Actions.cs @@ -19,6 +19,9 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; + +#nullable enable namespace OpenQA.Selenium.Interactions { @@ -29,9 +32,9 @@ public class Actions : IAction { private readonly TimeSpan duration; private ActionBuilder actionBuilder = new ActionBuilder(); - private PointerInputDevice activePointer; - private KeyInputDevice activeKeyboard; - private WheelInputDevice activeWheel; + private PointerInputDevice? activePointer; + private KeyInputDevice? activeKeyboard; + private WheelInputDevice? activeWheel; /// /// Initializes a new instance of the class. @@ -51,14 +54,10 @@ public Actions(IWebDriver driver) /// If does not implement . public Actions(IWebDriver driver, TimeSpan duration) { - IActionExecutor actionExecutor = GetDriverAs(driver); - if (actionExecutor == null) - { - throw new ArgumentException("The IWebDriver object must implement or wrap a driver that implements IActionExecutor.", nameof(driver)); - } + IActionExecutor actionExecutor = GetDriverAs(driver) + ?? throw new ArgumentException("The IWebDriver object must implement or wrap a driver that implements IActionExecutor.", nameof(driver)); this.ActionExecutor = actionExecutor; - this.duration = duration; } @@ -74,9 +73,10 @@ public Actions(IWebDriver driver, TimeSpan duration) /// The name of the pointer device to set as active. /// A self-reference to this Actions class. /// If a device with this name exists but is not a pointer. + [MemberNotNull(nameof(activePointer))] public Actions SetActivePointer(PointerKind kind, string name) { - InputDevice device = FindDeviceById(name); + InputDevice? device = FindDeviceById(name); this.activePointer = device switch { @@ -94,9 +94,10 @@ public Actions SetActivePointer(PointerKind kind, string name) /// The name of the keyboard device to set as active. /// A self-reference to this Actions class. /// If a device with this name exists but is not a keyboard. + [MemberNotNull(nameof(activeKeyboard))] public Actions SetActiveKeyboard(string name) { - InputDevice device = FindDeviceById(name); + InputDevice? device = FindDeviceById(name); this.activeKeyboard = device switch { @@ -114,9 +115,10 @@ public Actions SetActiveKeyboard(string name) /// The name of the wheel device to set as active. /// A self-reference to this Actions class. /// If a device with this name exists but is not a wheel. + [MemberNotNull(nameof(activeWheel))] public Actions SetActiveWheel(string name) { - InputDevice device = FindDeviceById(name); + InputDevice? device = FindDeviceById(name); this.activeWheel = device switch { @@ -128,7 +130,7 @@ public Actions SetActiveWheel(string name) return this; } - private InputDevice FindDeviceById(string name) + private InputDevice? FindDeviceById(string? name) { foreach (var sequence in this.actionBuilder.ToActionSequenceList()) { @@ -211,14 +213,14 @@ public Actions KeyDown(string theKey) /// of , , , /// , ,, /// ,. - public Actions KeyDown(IWebElement element, string theKey) + public Actions KeyDown(IWebElement? element, string theKey) { if (string.IsNullOrEmpty(theKey)) { throw new ArgumentException("The key value must not be null or empty", nameof(theKey)); } - ILocatable target = GetLocatableFromElement(element); + ILocatable? target = GetLocatableFromElement(element); if (element != null) { this.actionBuilder.AddAction(this.GetActivePointer().CreatePointerMove(element, 0, 0, duration)); @@ -255,14 +257,14 @@ public Actions KeyUp(string theKey) /// of , , , /// , ,, /// ,. - public Actions KeyUp(IWebElement element, string theKey) + public Actions KeyUp(IWebElement? element, string theKey) { if (string.IsNullOrEmpty(theKey)) { throw new ArgumentException("The key value must not be null or empty", nameof(theKey)); } - ILocatable target = GetLocatableFromElement(element); + ILocatable? target = GetLocatableFromElement(element); if (element != null) { this.actionBuilder.AddAction(this.GetActivePointer().CreatePointerMove(element, 0, 0, duration)); @@ -279,6 +281,7 @@ public Actions KeyUp(IWebElement element, string theKey) /// /// The keystrokes to send to the browser. /// A self-reference to this . + /// If is or . public Actions SendKeys(string keysToSend) { return this.SendKeys(null, keysToSend); @@ -290,14 +293,15 @@ public Actions SendKeys(string keysToSend) /// The element to which to send the keystrokes. /// The keystrokes to send to the browser. /// A self-reference to this . - public Actions SendKeys(IWebElement element, string keysToSend) + /// If is or . + public Actions SendKeys(IWebElement? element, string keysToSend) { if (string.IsNullOrEmpty(keysToSend)) { throw new ArgumentException("The key value must not be null or empty", nameof(keysToSend)); } - ILocatable target = GetLocatableFromElement(element); + ILocatable? target = GetLocatableFromElement(element); if (element != null) { this.actionBuilder.AddAction(this.GetActivePointer().CreatePointerMove(element, 0, 0, duration)); @@ -319,6 +323,7 @@ public Actions SendKeys(IWebElement element, string keysToSend) /// /// The element on which to click and hold. /// A self-reference to this . + /// If is null. public Actions ClickAndHold(IWebElement onElement) { this.MoveToElement(onElement).ClickAndHold(); @@ -340,6 +345,7 @@ public Actions ClickAndHold() /// /// The element on which to release the button. /// A self-reference to this . + /// If is null. public Actions Release(IWebElement onElement) { this.MoveToElement(onElement).Release(); @@ -361,6 +367,7 @@ public Actions Release() /// /// The element on which to click. /// A self-reference to this . + /// If is null. public Actions Click(IWebElement onElement) { this.MoveToElement(onElement).Click(); @@ -383,6 +390,7 @@ public Actions Click() /// /// The element on which to double-click. /// A self-reference to this . + /// If is null. public Actions DoubleClick(IWebElement onElement) { this.MoveToElement(onElement).DoubleClick(); @@ -407,6 +415,7 @@ public Actions DoubleClick() /// /// The element to which to move the mouse. /// A self-reference to this . + /// If is null. public Actions MoveToElement(IWebElement toElement) { if (toElement == null) @@ -460,6 +469,7 @@ public Actions MoveToLocation(int offsetX, int offsetY) /// /// The element on which to right-click. /// A self-reference to this . + /// If is null. public Actions ContextClick(IWebElement onElement) { this.MoveToElement(onElement).ContextClick(); @@ -483,6 +493,7 @@ public Actions ContextClick() /// The element on which the drag operation is started. /// The element on which the drop is performed. /// A self-reference to this . + /// If or are null. public Actions DragAndDrop(IWebElement source, IWebElement target) { this.ClickAndHold(source).MoveToElement(target).Release(target); @@ -496,6 +507,7 @@ public Actions DragAndDrop(IWebElement source, IWebElement target) /// The horizontal offset to which to move the mouse. /// The vertical offset to which to move the mouse. /// A self-reference to this . + /// If is null. public Actions DragAndDropToOffset(IWebElement source, int offsetX, int offsetY) { this.ClickAndHold(source).MoveByOffset(offsetX, offsetY).Release(); @@ -507,6 +519,7 @@ public Actions DragAndDropToOffset(IWebElement source, int offsetX, int offsetY) /// /// Which element to scroll into the viewport. /// A self-reference to this . + /// If is null. public Actions ScrollToElement(IWebElement element) { this.actionBuilder.AddAction(this.GetActiveWheel().CreateWheelScroll(element, 0, 0, 0, 0, duration)); @@ -540,8 +553,15 @@ public Actions ScrollByAmount(int deltaX, int deltaY) /// Distance along Y axis to scroll using the wheel. A negative value scrolls up. /// A self-reference to this . /// If the origin with offset is outside the viewport. + /// If is null. + /// If both or either of Viewport and Element are set. public Actions ScrollFromOrigin(WheelInputDevice.ScrollOrigin scrollOrigin, int deltaX, int deltaY) { + if (scrollOrigin is null) + { + throw new ArgumentNullException(nameof(scrollOrigin)); + } + if (scrollOrigin.Viewport && scrollOrigin.Element != null) { throw new ArgumentException("viewport can not be true if an element is defined.", nameof(scrollOrigin)); @@ -554,7 +574,7 @@ public Actions ScrollFromOrigin(WheelInputDevice.ScrollOrigin scrollOrigin, int } else { - this.actionBuilder.AddAction(this.GetActiveWheel().CreateWheelScroll(scrollOrigin.Element, + this.actionBuilder.AddAction(this.GetActiveWheel().CreateWheelScroll(scrollOrigin.Element!, scrollOrigin.XOffset, scrollOrigin.YOffset, deltaX, deltaY, duration)); } @@ -566,6 +586,7 @@ public Actions ScrollFromOrigin(WheelInputDevice.ScrollOrigin scrollOrigin, int /// /// How long to pause the action chain. /// A self-reference to this . + /// If is negative. public Actions Pause(TimeSpan duration) { this.actionBuilder.AddAction(new PauseInteraction(this.GetActivePointer(), duration)); @@ -603,15 +624,16 @@ public void Reset() /// /// The to get the location of. /// The of the . - protected static ILocatable GetLocatableFromElement(IWebElement element) + [return: NotNullIfNotNull(nameof(element))] + protected static ILocatable? GetLocatableFromElement(IWebElement? element) { if (element == null) { return null; } - ILocatable target = null; - IWrapsElement wrapper = element as IWrapsElement; + ILocatable? target = null; + IWrapsElement? wrapper = element as IWrapsElement; while (wrapper != null) { target = wrapper.WrappedElement as ILocatable; @@ -631,12 +653,12 @@ protected static ILocatable GetLocatableFromElement(IWebElement element) return target; } - private T GetDriverAs(IWebDriver driver) where T : class + private static T? GetDriverAs(IWebDriver? driver) where T : class { - T driverAsType = driver as T; + T? driverAsType = driver as T; if (driverAsType == null) { - IWrapsDriver wrapper = driver as IWrapsDriver; + IWrapsDriver? wrapper = driver as IWrapsDriver; while (wrapper != null) { driverAsType = wrapper.WrappedDriver as T; diff --git a/dotnet/src/webdriver/Interactions/InputDevice.cs b/dotnet/src/webdriver/Interactions/InputDevice.cs index f73c0578e2586..f3adbf78128a8 100644 --- a/dotnet/src/webdriver/Interactions/InputDevice.cs +++ b/dotnet/src/webdriver/Interactions/InputDevice.cs @@ -77,6 +77,7 @@ public Interaction CreatePause() /// of the pause. Note that pauses to synchronize /// with other action sequences for other devices. /// The representing the action. + /// If is negative. public Interaction CreatePause(TimeSpan duration) { return new PauseInteraction(this, duration); diff --git a/dotnet/src/webdriver/Interactions/PauseInteraction.cs b/dotnet/src/webdriver/Interactions/PauseInteraction.cs index d571f2c30a1f0..6510ef24b51af 100644 --- a/dotnet/src/webdriver/Interactions/PauseInteraction.cs +++ b/dotnet/src/webdriver/Interactions/PauseInteraction.cs @@ -46,6 +46,7 @@ public PauseInteraction(InputDevice sourceDevice) /// /// The input device on which to execute the pause. /// The length of time to pause for. + /// If is negative. public PauseInteraction(InputDevice sourceDevice, TimeSpan duration) : base(sourceDevice) { From b9caf48cd246b6d9445968cc3a4f8c30fad8aedf Mon Sep 17 00:00:00 2001 From: Michael Render Date: Sat, 1 Feb 2025 01:23:47 -0500 Subject: [PATCH 2/2] Add `Keys` nullability --- dotnet/src/webdriver/Keys.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/dotnet/src/webdriver/Keys.cs b/dotnet/src/webdriver/Keys.cs index 7fa250a4c2720..c824c68f86acf 100644 --- a/dotnet/src/webdriver/Keys.cs +++ b/dotnet/src/webdriver/Keys.cs @@ -21,6 +21,8 @@ using System.Collections.Generic; using System.Globalization; +#nullable enable + namespace OpenQA.Selenium { /// @@ -352,13 +354,14 @@ public static class Keys /// public static readonly string ZenkakuHankaku = Convert.ToString(Convert.ToChar(0xE040, CultureInfo.InvariantCulture), CultureInfo.InvariantCulture); - private static Dictionary descriptions; + private static Dictionary? descriptions; /// /// Gets the description of a specific key. /// /// The key value for which to get the description. /// The description of the key. + /// If is . internal static object GetDescription(string value) { if (descriptions == null) @@ -423,9 +426,9 @@ internal static object GetDescription(string value) descriptions.Add(ZenkakuHankaku, "Zenkaku Hankaku"); } - if (descriptions.ContainsKey(value)) + if (descriptions.TryGetValue(value, out string? description)) { - return descriptions[value]; + return description; } return value;