Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[dotnet] Annotate nullability on Actions type #15208

Merged
merged 2 commits into from
Feb 1, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 48 additions & 26 deletions dotnet/src/webdriver/Interactions/Actions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@

using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;

#nullable enable

namespace OpenQA.Selenium.Interactions
{
Expand All @@ -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;

/// <summary>
/// Initializes a new instance of the <see cref="Actions"/> class.
Expand All @@ -51,14 +54,10 @@ public Actions(IWebDriver driver)
/// <exception cref="ArgumentException">If <paramref name="driver"/> does not implement <see cref="IActionExecutor"/>.</exception>
public Actions(IWebDriver driver, TimeSpan duration)
{
IActionExecutor actionExecutor = GetDriverAs<IActionExecutor>(driver);
if (actionExecutor == null)
{
throw new ArgumentException("The IWebDriver object must implement or wrap a driver that implements IActionExecutor.", nameof(driver));
}
IActionExecutor actionExecutor = GetDriverAs<IActionExecutor>(driver)
?? throw new ArgumentException("The IWebDriver object must implement or wrap a driver that implements IActionExecutor.", nameof(driver));

this.ActionExecutor = actionExecutor;

this.duration = duration;
}

Expand All @@ -74,9 +73,10 @@ public Actions(IWebDriver driver, TimeSpan duration)
/// <param name="name">The name of the pointer device to set as active.</param>
/// <returns>A self-reference to this Actions class.</returns>
/// <exception cref="InvalidOperationException">If a device with this name exists but is not a pointer.</exception>
[MemberNotNull(nameof(activePointer))]
public Actions SetActivePointer(PointerKind kind, string name)
{
InputDevice device = FindDeviceById(name);
InputDevice? device = FindDeviceById(name);

this.activePointer = device switch
{
Expand All @@ -94,9 +94,10 @@ public Actions SetActivePointer(PointerKind kind, string name)
/// <param name="name">The name of the keyboard device to set as active.</param>
/// <returns>A self-reference to this Actions class.</returns>
/// <exception cref="InvalidOperationException">If a device with this name exists but is not a keyboard.</exception>
[MemberNotNull(nameof(activeKeyboard))]
public Actions SetActiveKeyboard(string name)
{
InputDevice device = FindDeviceById(name);
InputDevice? device = FindDeviceById(name);

this.activeKeyboard = device switch
{
Expand All @@ -114,9 +115,10 @@ public Actions SetActiveKeyboard(string name)
/// <param name="name">The name of the wheel device to set as active.</param>
/// <returns>A self-reference to this Actions class.</returns>
/// <exception cref="InvalidOperationException">If a device with this name exists but is not a wheel.</exception>
[MemberNotNull(nameof(activeWheel))]
public Actions SetActiveWheel(string name)
{
InputDevice device = FindDeviceById(name);
InputDevice? device = FindDeviceById(name);

this.activeWheel = device switch
{
Expand All @@ -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())
{
Expand Down Expand Up @@ -211,14 +213,14 @@ public Actions KeyDown(string theKey)
/// of <see cref="Keys.Shift"/>, <see cref="Keys.Control"/>, <see cref="Keys.Alt"/>,
/// <see cref="Keys.Meta"/>, <see cref="Keys.Command"/>,<see cref="Keys.LeftAlt"/>,
/// <see cref="Keys.LeftControl"/>,<see cref="Keys.LeftShift"/>.</exception>
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));
Expand Down Expand Up @@ -255,14 +257,14 @@ public Actions KeyUp(string theKey)
/// of <see cref="Keys.Shift"/>, <see cref="Keys.Control"/>, <see cref="Keys.Alt"/>,
/// <see cref="Keys.Meta"/>, <see cref="Keys.Command"/>,<see cref="Keys.LeftAlt"/>,
/// <see cref="Keys.LeftControl"/>,<see cref="Keys.LeftShift"/>.</exception>
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));
Expand All @@ -279,6 +281,7 @@ public Actions KeyUp(IWebElement element, string theKey)
/// </summary>
/// <param name="keysToSend">The keystrokes to send to the browser.</param>
/// <returns>A self-reference to this <see cref="Actions"/>.</returns>
/// <exception cref="ArgumentException">If <paramref name="keysToSend"/> is <see langword="null"/> or <see cref="string.Empty"/>.</exception>
public Actions SendKeys(string keysToSend)
{
return this.SendKeys(null, keysToSend);
Expand All @@ -290,14 +293,15 @@ public Actions SendKeys(string keysToSend)
/// <param name="element">The element to which to send the keystrokes.</param>
/// <param name="keysToSend">The keystrokes to send to the browser.</param>
/// <returns>A self-reference to this <see cref="Actions"/>.</returns>
public Actions SendKeys(IWebElement element, string keysToSend)
/// <exception cref="ArgumentException">If <paramref name="keysToSend"/> is <see langword="null"/> or <see cref="string.Empty"/>.</exception>
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));
Expand All @@ -319,6 +323,7 @@ public Actions SendKeys(IWebElement element, string keysToSend)
/// </summary>
/// <param name="onElement">The element on which to click and hold.</param>
/// <returns>A self-reference to this <see cref="Actions"/>.</returns>
/// <exception cref="ArgumentNullException">If <paramref name="onElement"/> is null.</exception>
public Actions ClickAndHold(IWebElement onElement)
{
this.MoveToElement(onElement).ClickAndHold();
Expand All @@ -340,6 +345,7 @@ public Actions ClickAndHold()
/// </summary>
/// <param name="onElement">The element on which to release the button.</param>
/// <returns>A self-reference to this <see cref="Actions"/>.</returns>
/// <exception cref="ArgumentNullException">If <paramref name="onElement"/> is null.</exception>
public Actions Release(IWebElement onElement)
{
this.MoveToElement(onElement).Release();
Expand All @@ -361,6 +367,7 @@ public Actions Release()
/// </summary>
/// <param name="onElement">The element on which to click.</param>
/// <returns>A self-reference to this <see cref="Actions"/>.</returns>
/// <exception cref="ArgumentNullException">If <paramref name="onElement"/> is null.</exception>
public Actions Click(IWebElement onElement)
{
this.MoveToElement(onElement).Click();
Expand All @@ -383,6 +390,7 @@ public Actions Click()
/// </summary>
/// <param name="onElement">The element on which to double-click.</param>
/// <returns>A self-reference to this <see cref="Actions"/>.</returns>
/// <exception cref="ArgumentNullException">If <paramref name="onElement"/> is null.</exception>
public Actions DoubleClick(IWebElement onElement)
{
this.MoveToElement(onElement).DoubleClick();
Expand All @@ -407,6 +415,7 @@ public Actions DoubleClick()
/// </summary>
/// <param name="toElement">The element to which to move the mouse.</param>
/// <returns>A self-reference to this <see cref="Actions"/>.</returns>
/// <exception cref="ArgumentNullException">If <paramref name="toElement"/> is null.</exception>
public Actions MoveToElement(IWebElement toElement)
{
if (toElement == null)
Expand Down Expand Up @@ -460,6 +469,7 @@ public Actions MoveToLocation(int offsetX, int offsetY)
/// </summary>
/// <param name="onElement">The element on which to right-click.</param>
/// <returns>A self-reference to this <see cref="Actions"/>.</returns>
/// <exception cref="ArgumentNullException">If <paramref name="onElement"/> is null.</exception>
public Actions ContextClick(IWebElement onElement)
{
this.MoveToElement(onElement).ContextClick();
Expand All @@ -483,6 +493,7 @@ public Actions ContextClick()
/// <param name="source">The element on which the drag operation is started.</param>
/// <param name="target">The element on which the drop is performed.</param>
/// <returns>A self-reference to this <see cref="Actions"/>.</returns>
/// <exception cref="ArgumentNullException">If <paramref name="source"/> or <paramref name="target"/> are null.</exception>
public Actions DragAndDrop(IWebElement source, IWebElement target)
{
this.ClickAndHold(source).MoveToElement(target).Release(target);
Expand All @@ -496,6 +507,7 @@ public Actions DragAndDrop(IWebElement source, IWebElement target)
/// <param name="offsetX">The horizontal offset to which to move the mouse.</param>
/// <param name="offsetY">The vertical offset to which to move the mouse.</param>
/// <returns>A self-reference to this <see cref="Actions"/>.</returns>
/// <exception cref="ArgumentNullException">If <paramref name="source"/> is null.</exception>
public Actions DragAndDropToOffset(IWebElement source, int offsetX, int offsetY)
{
this.ClickAndHold(source).MoveByOffset(offsetX, offsetY).Release();
Expand All @@ -507,6 +519,7 @@ public Actions DragAndDropToOffset(IWebElement source, int offsetX, int offsetY)
/// </summary>
/// <param name="element">Which element to scroll into the viewport.</param>
/// <returns>A self-reference to this <see cref="Actions"/>.</returns>
/// <exception cref="ArgumentNullException">If <paramref name="element"/> is null.</exception>
public Actions ScrollToElement(IWebElement element)
{
this.actionBuilder.AddAction(this.GetActiveWheel().CreateWheelScroll(element, 0, 0, 0, 0, duration));
Expand Down Expand Up @@ -540,8 +553,15 @@ public Actions ScrollByAmount(int deltaX, int deltaY)
/// <param name="deltaY">Distance along Y axis to scroll using the wheel. A negative value scrolls up.</param>
/// <returns>A self-reference to this <see cref="Actions"/>.</returns>
/// <exception cref="MoveTargetOutOfBoundsException">If the origin with offset is outside the viewport.</exception>
/// <exception cref="ArgumentNullException">If <paramref name="scrollOrigin"/> is null.</exception>
/// <exception cref="ArgumentException">If both or either of Viewport and Element are set.</exception>
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));
Expand All @@ -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));
}

Expand All @@ -566,6 +586,7 @@ public Actions ScrollFromOrigin(WheelInputDevice.ScrollOrigin scrollOrigin, int
/// </summary>
/// <param name="duration">How long to pause the action chain.</param>
/// <returns>A self-reference to this <see cref="Actions"/>.</returns>
/// <exception cref="ArgumentException">If <paramref name="duration"/> is negative.</exception>
public Actions Pause(TimeSpan duration)
{
this.actionBuilder.AddAction(new PauseInteraction(this.GetActivePointer(), duration));
Expand Down Expand Up @@ -603,15 +624,16 @@ public void Reset()
/// </summary>
/// <param name="element">The <see cref="IWebElement"/> to get the location of.</param>
/// <returns>The <see cref="ILocatable"/> of the <see cref="IWebElement"/>.</returns>
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;
Expand All @@ -631,12 +653,12 @@ protected static ILocatable GetLocatableFromElement(IWebElement element)
return target;
}

private T GetDriverAs<T>(IWebDriver driver) where T : class
private static T? GetDriverAs<T>(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;
Expand Down
1 change: 1 addition & 0 deletions dotnet/src/webdriver/Interactions/InputDevice.cs
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ public Interaction CreatePause()
/// of the pause. Note that <see cref="TimeSpan.Zero"/> pauses to synchronize
/// with other action sequences for other devices.</param>
/// <returns>The <see cref="Interaction"/> representing the action.</returns>
/// <exception cref="ArgumentException">If <paramref name="duration"/> is negative.</exception>
public Interaction CreatePause(TimeSpan duration)
{
return new PauseInteraction(this, duration);
Expand Down
1 change: 1 addition & 0 deletions dotnet/src/webdriver/Interactions/PauseInteraction.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ public PauseInteraction(InputDevice sourceDevice)
/// </summary>
/// <param name="sourceDevice">The input device on which to execute the pause.</param>
/// <param name="duration">The length of time to pause for.</param>
/// <exception cref="ArgumentException">If <paramref name="duration"/> is negative.</exception>
public PauseInteraction(InputDevice sourceDevice, TimeSpan duration)
: base(sourceDevice)
{
Expand Down
9 changes: 6 additions & 3 deletions dotnet/src/webdriver/Keys.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
using System.Collections.Generic;
using System.Globalization;

#nullable enable

namespace OpenQA.Selenium
{
/// <summary>
Expand Down Expand Up @@ -352,13 +354,14 @@ public static class Keys
/// </summary>
public static readonly string ZenkakuHankaku = Convert.ToString(Convert.ToChar(0xE040, CultureInfo.InvariantCulture), CultureInfo.InvariantCulture);

private static Dictionary<string, string> descriptions;
private static Dictionary<string, string>? descriptions;

/// <summary>
/// Gets the description of a specific key.
/// </summary>
/// <param name="value">The key value for which to get the description.</param>
/// <returns>The description of the key.</returns>
/// <exception cref="ArgumentNullException">If <paramref name="value"/> is <see langword="null"/>.</exception>
internal static object GetDescription(string value)
{
if (descriptions == null)
Expand Down Expand Up @@ -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;
Expand Down