Skip to content
90 changes: 33 additions & 57 deletions dotnet/src/webdriver/Interactions/Actions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,23 +31,23 @@ public class Actions : IAction
private PointerInputDevice activePointer;
private KeyInputDevice activeKeyboard;
private WheelInputDevice activeWheel;
private IActionExecutor actionExecutor;

/// <summary>
/// Initializes a new instance of the <see cref="Actions"/> class.
/// </summary>
/// <param name="driver">The <see cref="IWebDriver"/> object on which the actions built will be performed.</param>
/// <exception cref="ArgumentException">If <paramref name="driver"/> does not implement IActionExecutor.</exception>
public Actions(IWebDriver driver)
: this(driver, TimeSpan.FromMilliseconds(250))
{

}

/// <summary>
/// Initializes a new instance of the <see cref="Actions"/> class.
/// </summary>
/// <param name="driver">The <see cref="IWebDriver"/> object on which the actions built will be performed.</param>
/// <param name="duration">How long durable action is expected to take.</param>
/// <exception cref="ArgumentException">If <paramref name="driver"/> does not implement IActionExecutor.</exception>
public Actions(IWebDriver driver, TimeSpan duration)
{
IActionExecutor actionExecutor = GetDriverAs<IActionExecutor>(driver);
Expand All @@ -56,51 +56,35 @@ public Actions(IWebDriver driver, TimeSpan duration)
throw new ArgumentException("The IWebDriver object must implement or wrap a driver that implements IActionExecutor.", nameof(driver));
}

this.actionExecutor = actionExecutor;
this.ActionExecutor = actionExecutor;

this.duration = duration;
}

/// <summary>
/// Returns the <see cref="IActionExecutor"/> for the driver.
/// </summary>
protected IActionExecutor ActionExecutor
{
get { return this.actionExecutor; }
}
protected IActionExecutor ActionExecutor { get; }

/// <summary>
/// Sets the active pointer device for this Actions class.
/// </summary>
/// <param name="kind">The kind of pointer device to set as active.</param>
/// <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>
public Actions SetActivePointer(PointerKind kind, string name)
{
IList<ActionSequence> sequences = this.actionBuilder.ToActionSequenceList();

InputDevice device = null;

foreach (var sequence in sequences)
{
Dictionary<string, object> actions = sequence.ToDictionary();

string id = (string)actions["id"];

if (id == name)
{
device = sequence.inputDevice;
break;
}
}
InputDevice device = FindDeviceById(name);

if (device == null)
{
this.activePointer = new PointerInputDevice(kind, name);
}
else
{
this.activePointer = (PointerInputDevice)device;
this.activePointer = device as PointerInputDevice
?? throw new InvalidOperationException($"Device under the name \"{name}\" is not a pointer. Actual input type: {device.DeviceKind}");
}

return this;
Expand All @@ -111,32 +95,20 @@ public Actions SetActivePointer(PointerKind kind, string name)
/// </summary>
/// <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>
public Actions SetActiveKeyboard(string name)
{
IList<ActionSequence> sequences = this.actionBuilder.ToActionSequenceList();

InputDevice device = null;

foreach (var sequence in sequences)
{
Dictionary<string, object> actions = sequence.ToDictionary();

string id = (string)actions["id"];

if (id == name)
{
device = sequence.inputDevice;
break;
}
}
InputDevice device = FindDeviceById(name);

if (device == null)
{
this.activeKeyboard = new KeyInputDevice(name);
}
else
{
this.activeKeyboard = (KeyInputDevice)device;
this.activeKeyboard = device as KeyInputDevice
?? throw new InvalidOperationException($"Device under the name \"{name}\" is not a keyboard. Actual input type: {device.DeviceKind}");

}

return this;
Expand All @@ -147,35 +119,39 @@ public Actions SetActiveKeyboard(string name)
/// </summary>
/// <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>
public Actions SetActiveWheel(string name)
{
IList<ActionSequence> sequences = this.actionBuilder.ToActionSequenceList();
InputDevice device = FindDeviceById(name);

InputDevice device = null;
if (device == null)
{
this.activeWheel = new WheelInputDevice(name);
}
else
{
this.activeWheel = device as WheelInputDevice
?? throw new InvalidOperationException($"Device under the name \"{name}\" is not a wheel. Actual input type: {device.DeviceKind}");
}

foreach (var sequence in sequences)
return this;
}

private InputDevice FindDeviceById(string name)
{
foreach (var sequence in this.actionBuilder.ToActionSequenceList())
{
Dictionary<string, object> actions = sequence.ToDictionary();

string id = (string)actions["id"];

if (id == name)
{
device = sequence.inputDevice;
break;
return sequence.inputDevice;
}
}

if (device == null)
{
this.activeWheel = new WheelInputDevice(name);
}
else
{
this.activeWheel = (WheelInputDevice)device;
}

return this;
return null;
}

/// <summary>
Expand Down Expand Up @@ -619,7 +595,7 @@ public IAction Build()
/// </summary>
public void Perform()
{
this.actionExecutor.PerformActions(this.actionBuilder.ToActionSequenceList());
this.ActionExecutor.PerformActions(this.actionBuilder.ToActionSequenceList());
this.actionBuilder.ClearSequences();
}

Expand Down
33 changes: 33 additions & 0 deletions dotnet/test/common/Interactions/CombinedInputActionsTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -397,6 +397,39 @@ public void PerformsPause()
Assert.IsTrue(DateTime.Now - start > TimeSpan.FromMilliseconds(1200));
}


[Test]
public void ShouldHandleClashingDeviceNamesGracefully()
{
var actionsWithPointer = new Actions(driver)
.SetActivePointer(PointerKind.Mouse, "test")
.Click();

Assert.That(() =>
{
actionsWithPointer.SetActiveWheel("test");
}, Throws.InvalidOperationException.With.Message.EqualTo("Device under the name \"test\" is not a wheel. Actual input type: Pointer"));

var actionsWithKeyboard = new Actions(driver)
.SetActiveKeyboard("test")
.KeyDown(Keys.Shift).KeyUp(Keys.Shift);

Assert.That(() =>
{
actionsWithKeyboard.SetActivePointer(PointerKind.Pen, "test");
}, Throws.InvalidOperationException.With.Message.EqualTo("Device under the name \"test\" is not a pointer. Actual input type: Key"));

var actionsWithWheel = new Actions(driver)
.SetActiveWheel("test")
.ScrollByAmount(0, 0);

Assert.That(() =>
{
actionsWithWheel.SetActiveKeyboard("test");
}, Throws.InvalidOperationException.With.Message.EqualTo("Device under the name \"test\" is not a keyboard. Actual input type: Wheel"));
}


private bool FuzzyPositionMatching(int expectedX, int expectedY, string locationTuple)
{
string[] splitString = locationTuple.Split(',');
Expand Down