Skip to content

Commit a9ec9ca

Browse files
[dotnet] Gracefully handle clashing device names in Actions (#14713)
1 parent cc57549 commit a9ec9ca

File tree

3 files changed

+78
-79
lines changed

3 files changed

+78
-79
lines changed

Diff for: dotnet/src/webdriver/Interactions/ActionSequence.cs

+12-10
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@ namespace OpenQA.Selenium.Interactions
2929
public class ActionSequence
3030
{
3131
private List<Interaction> interactions = new List<Interaction>();
32-
private InputDevice device;
3332

3433
/// <summary>
3534
/// Initializes a new instance of the <see cref="ActionSequence"/> class.
@@ -52,7 +51,7 @@ public ActionSequence(InputDevice device, int initialSize)
5251
throw new ArgumentNullException(nameof(device), "Input device cannot be null.");
5352
}
5453

55-
this.device = device;
54+
this.InputDevice = device;
5655

5756
for (int i = 0; i < initialSize; i++)
5857
{
@@ -71,10 +70,13 @@ public int Count
7170
/// <summary>
7271
/// Gets the input device for this Action sequence.
7372
/// </summary>
74-
public InputDevice inputDevice
75-
{
76-
get { return this.inputDevice; }
77-
}
73+
[Obsolete("This property has been renamed to InputDevice and will be removed in a future version")]
74+
public InputDevice inputDevice => InputDevice;
75+
76+
/// <summary>
77+
/// Gets the input device for this Action sequence.
78+
/// </summary>
79+
public InputDevice InputDevice { get; }
7880

7981
/// <summary>
8082
/// Adds an action to the sequence.
@@ -88,9 +90,9 @@ public ActionSequence AddAction(Interaction interactionToAdd)
8890
throw new ArgumentNullException(nameof(interactionToAdd), "Interaction to add to sequence must not be null");
8991
}
9092

91-
if (!interactionToAdd.IsValidFor(this.device.DeviceKind))
93+
if (!interactionToAdd.IsValidFor(this.InputDevice.DeviceKind))
9294
{
93-
throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, "Interaction {0} is invalid for device type {1}.", interactionToAdd.GetType(), this.device.DeviceKind), nameof(interactionToAdd));
95+
throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, "Interaction {0} is invalid for device type {1}.", interactionToAdd.GetType(), this.InputDevice.DeviceKind), nameof(interactionToAdd));
9496
}
9597

9698
this.interactions.Add(interactionToAdd);
@@ -103,7 +105,7 @@ public ActionSequence AddAction(Interaction interactionToAdd)
103105
/// <returns>A <see cref="Dictionary{TKey, TValue}"/> containing the actions in this sequence.</returns>
104106
public Dictionary<string, object> ToDictionary()
105107
{
106-
Dictionary<string, object> toReturn = this.device.ToDictionary();
108+
Dictionary<string, object> toReturn = this.InputDevice.ToDictionary();
107109

108110
List<object> encodedActions = new List<object>();
109111
foreach (Interaction action in this.interactions)
@@ -122,7 +124,7 @@ public Dictionary<string, object> ToDictionary()
122124
/// <returns>A string that represents the current <see cref="ActionSequence"/>.</returns>
123125
public override string ToString()
124126
{
125-
StringBuilder builder = new StringBuilder("Action sequence - ").Append(this.device.ToString());
127+
StringBuilder builder = new StringBuilder("Action sequence - ").Append(this.InputDevice.ToString());
126128
foreach (Interaction action in this.interactions)
127129
{
128130
builder.AppendLine();

Diff for: dotnet/src/webdriver/Interactions/Actions.cs

+35-69
Original file line numberDiff line numberDiff line change
@@ -31,23 +31,23 @@ public class Actions : IAction
3131
private PointerInputDevice activePointer;
3232
private KeyInputDevice activeKeyboard;
3333
private WheelInputDevice activeWheel;
34-
private IActionExecutor actionExecutor;
3534

3635
/// <summary>
3736
/// Initializes a new instance of the <see cref="Actions"/> class.
3837
/// </summary>
3938
/// <param name="driver">The <see cref="IWebDriver"/> object on which the actions built will be performed.</param>
39+
/// <exception cref="ArgumentException">If <paramref name="driver"/> does not implement <see cref="IActionExecutor"/>.</exception>
4040
public Actions(IWebDriver driver)
4141
: this(driver, TimeSpan.FromMilliseconds(250))
4242
{
43-
4443
}
4544

4645
/// <summary>
4746
/// Initializes a new instance of the <see cref="Actions"/> class.
4847
/// </summary>
4948
/// <param name="driver">The <see cref="IWebDriver"/> object on which the actions built will be performed.</param>
5049
/// <param name="duration">How long durable action is expected to take.</param>
50+
/// <exception cref="ArgumentException">If <paramref name="driver"/> does not implement <see cref="IActionExecutor"/>.</exception>
5151
public Actions(IWebDriver driver, TimeSpan duration)
5252
{
5353
IActionExecutor actionExecutor = GetDriverAs<IActionExecutor>(driver);
@@ -56,52 +56,33 @@ public Actions(IWebDriver driver, TimeSpan duration)
5656
throw new ArgumentException("The IWebDriver object must implement or wrap a driver that implements IActionExecutor.", nameof(driver));
5757
}
5858

59-
this.actionExecutor = actionExecutor;
59+
this.ActionExecutor = actionExecutor;
6060

6161
this.duration = duration;
6262
}
6363

6464
/// <summary>
6565
/// Returns the <see cref="IActionExecutor"/> for the driver.
6666
/// </summary>
67-
protected IActionExecutor ActionExecutor
68-
{
69-
get { return this.actionExecutor; }
70-
}
67+
protected IActionExecutor ActionExecutor { get; }
7168

7269
/// <summary>
7370
/// Sets the active pointer device for this Actions class.
7471
/// </summary>
7572
/// <param name="kind">The kind of pointer device to set as active.</param>
7673
/// <param name="name">The name of the pointer device to set as active.</param>
7774
/// <returns>A self-reference to this Actions class.</returns>
75+
/// <exception cref="InvalidOperationException">If a device with this name exists but is not a pointer.</exception>
7876
public Actions SetActivePointer(PointerKind kind, string name)
7977
{
80-
IList<ActionSequence> sequences = this.actionBuilder.ToActionSequenceList();
78+
InputDevice device = FindDeviceById(name);
8179

82-
InputDevice device = null;
83-
84-
foreach (var sequence in sequences)
80+
this.activePointer = device switch
8581
{
86-
Dictionary<string, object> actions = sequence.ToDictionary();
87-
88-
string id = (string)actions["id"];
89-
90-
if (id == name)
91-
{
92-
device = sequence.inputDevice;
93-
break;
94-
}
95-
}
96-
97-
if (device == null)
98-
{
99-
this.activePointer = new PointerInputDevice(kind, name);
100-
}
101-
else
102-
{
103-
this.activePointer = (PointerInputDevice)device;
104-
}
82+
null => new PointerInputDevice(kind, name),
83+
PointerInputDevice pointerDevice => pointerDevice,
84+
_ => throw new InvalidOperationException($"Device under the name \"{name}\" is not a pointer. Actual input type: {device.DeviceKind}"),
85+
};
10586

10687
return this;
10788
}
@@ -111,33 +92,17 @@ public Actions SetActivePointer(PointerKind kind, string name)
11192
/// </summary>
11293
/// <param name="name">The name of the keyboard device to set as active.</param>
11394
/// <returns>A self-reference to this Actions class.</returns>
95+
/// <exception cref="InvalidOperationException">If a device with this name exists but is not a keyboard.</exception>
11496
public Actions SetActiveKeyboard(string name)
11597
{
116-
IList<ActionSequence> sequences = this.actionBuilder.ToActionSequenceList();
98+
InputDevice device = FindDeviceById(name);
11799

118-
InputDevice device = null;
119-
120-
foreach (var sequence in sequences)
100+
this.activeKeyboard = device switch
121101
{
122-
Dictionary<string, object> actions = sequence.ToDictionary();
123-
124-
string id = (string)actions["id"];
125-
126-
if (id == name)
127-
{
128-
device = sequence.inputDevice;
129-
break;
130-
}
131-
}
132-
133-
if (device == null)
134-
{
135-
this.activeKeyboard = new KeyInputDevice(name);
136-
}
137-
else
138-
{
139-
this.activeKeyboard = (KeyInputDevice)device;
140-
}
102+
null => new KeyInputDevice(name),
103+
KeyInputDevice keyDevice => keyDevice,
104+
_ => throw new InvalidOperationException($"Device under the name \"{name}\" is not a keyboard. Actual input type: {device.DeviceKind}"),
105+
};
141106

142107
return this;
143108
}
@@ -147,35 +112,36 @@ public Actions SetActiveKeyboard(string name)
147112
/// </summary>
148113
/// <param name="name">The name of the wheel device to set as active.</param>
149114
/// <returns>A self-reference to this Actions class.</returns>
115+
/// <exception cref="InvalidOperationException">If a device with this name exists but is not a wheel.</exception>
150116
public Actions SetActiveWheel(string name)
151117
{
152-
IList<ActionSequence> sequences = this.actionBuilder.ToActionSequenceList();
118+
InputDevice device = FindDeviceById(name);
119+
120+
this.activeWheel = device switch
121+
{
122+
null => new WheelInputDevice(name),
123+
WheelInputDevice wheelDevice => wheelDevice,
124+
_ => throw new InvalidOperationException($"Device under the name \"{name}\" is not a wheel. Actual input type: {device.DeviceKind}"),
125+
};
153126

154-
InputDevice device = null;
127+
return this;
128+
}
155129

156-
foreach (var sequence in sequences)
130+
private InputDevice FindDeviceById(string name)
131+
{
132+
foreach (var sequence in this.actionBuilder.ToActionSequenceList())
157133
{
158134
Dictionary<string, object> actions = sequence.ToDictionary();
159135

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

162138
if (id == name)
163139
{
164-
device = sequence.inputDevice;
165-
break;
140+
return sequence.inputDevice;
166141
}
167142
}
168143

169-
if (device == null)
170-
{
171-
this.activeWheel = new WheelInputDevice(name);
172-
}
173-
else
174-
{
175-
this.activeWheel = (WheelInputDevice)device;
176-
}
177-
178-
return this;
144+
return null;
179145
}
180146

181147
/// <summary>
@@ -619,7 +585,7 @@ public IAction Build()
619585
/// </summary>
620586
public void Perform()
621587
{
622-
this.actionExecutor.PerformActions(this.actionBuilder.ToActionSequenceList());
588+
this.ActionExecutor.PerformActions(this.actionBuilder.ToActionSequenceList());
623589
this.actionBuilder.ClearSequences();
624590
}
625591

Diff for: dotnet/test/common/Interactions/CombinedInputActionsTest.cs

+31
Original file line numberDiff line numberDiff line change
@@ -397,6 +397,37 @@ public void PerformsPause()
397397
Assert.IsTrue(DateTime.Now - start > TimeSpan.FromMilliseconds(1200));
398398
}
399399

400+
[Test]
401+
public void ShouldHandleClashingDeviceNamesGracefully()
402+
{
403+
var actionsWithPointer = new Actions(driver)
404+
.SetActivePointer(PointerKind.Mouse, "test")
405+
.Click();
406+
407+
Assert.That(() =>
408+
{
409+
actionsWithPointer.SetActiveWheel("test");
410+
}, Throws.InvalidOperationException.With.Message.EqualTo("Device under the name \"test\" is not a wheel. Actual input type: Pointer"));
411+
412+
var actionsWithKeyboard = new Actions(driver)
413+
.SetActiveKeyboard("test")
414+
.KeyDown(Keys.Shift).KeyUp(Keys.Shift);
415+
416+
Assert.That(() =>
417+
{
418+
actionsWithKeyboard.SetActivePointer(PointerKind.Pen, "test");
419+
}, Throws.InvalidOperationException.With.Message.EqualTo("Device under the name \"test\" is not a pointer. Actual input type: Key"));
420+
421+
var actionsWithWheel = new Actions(driver)
422+
.SetActiveWheel("test")
423+
.ScrollByAmount(0, 0);
424+
425+
Assert.That(() =>
426+
{
427+
actionsWithWheel.SetActiveKeyboard("test");
428+
}, Throws.InvalidOperationException.With.Message.EqualTo("Device under the name \"test\" is not a keyboard. Actual input type: Wheel"));
429+
}
430+
400431
private bool FuzzyPositionMatching(int expectedX, int expectedY, string locationTuple)
401432
{
402433
string[] splitString = locationTuple.Split(',');

0 commit comments

Comments
 (0)