blob: 1018a8be10c73fb14694d69ebd32974c9de5d873 [file] [log] [blame] [edit]
// <copyright file="Actions.cs" company="Selenium Committers">
// Licensed to the Software Freedom Conservancy (SFC) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The SFC licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
// </copyright>
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
namespace OpenQA.Selenium.Interactions;
/// <summary>
/// Provides a mechanism for building advanced interactions with the browser.
/// </summary>
public class Actions : IAction
{
private readonly TimeSpan duration;
private ActionBuilder actionBuilder = new ActionBuilder();
private PointerInputDevice? activePointer;
private KeyInputDevice? activeKeyboard;
private WheelInputDevice? activeWheel;
/// <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 <see cref="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 <see cref="IActionExecutor"/>.</exception>
public Actions(IWebDriver driver, TimeSpan duration)
{
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;
}
/// <summary>
/// Returns the <see cref="IActionExecutor"/> for the driver.
/// </summary>
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>
[MemberNotNull(nameof(activePointer))]
public Actions SetActivePointer(PointerKind kind, string name)
{
InputDevice? device = FindDeviceById(name);
this.activePointer = device switch
{
null => new PointerInputDevice(kind, name),
PointerInputDevice pointerDevice => pointerDevice,
_ => throw new InvalidOperationException($"Device under the name \"{name}\" is not a pointer. Actual input type: {device.DeviceKind}"),
};
return this;
}
/// <summary>
/// Sets the active keyboard device for this Actions class.
/// </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>
[MemberNotNull(nameof(activeKeyboard))]
public Actions SetActiveKeyboard(string name)
{
InputDevice? device = FindDeviceById(name);
this.activeKeyboard = device switch
{
null => new KeyInputDevice(name),
KeyInputDevice keyDevice => keyDevice,
_ => throw new InvalidOperationException($"Device under the name \"{name}\" is not a keyboard. Actual input type: {device.DeviceKind}"),
};
return this;
}
/// <summary>
/// Sets the active wheel device for this Actions class.
/// </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>
[MemberNotNull(nameof(activeWheel))]
public Actions SetActiveWheel(string name)
{
InputDevice? device = FindDeviceById(name);
this.activeWheel = device switch
{
null => new WheelInputDevice(name),
WheelInputDevice wheelDevice => wheelDevice,
_ => throw new InvalidOperationException($"Device under the name \"{name}\" is not a wheel. Actual input type: {device.DeviceKind}"),
};
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)
{
return sequence.InputDevice;
}
}
return null;
}
/// <summary>
/// Gets the active pointer device for this Actions class.
/// </summary>
/// <returns>The active pointer device for this Actions class.</returns>
public PointerInputDevice GetActivePointer()
{
if (this.activePointer == null)
{
SetActivePointer(PointerKind.Mouse, "default mouse");
}
return this.activePointer;
}
/// <summary>
/// Gets the active keyboard device for this Actions class.
/// </summary>
/// <returns>The active keyboard device for this Actions class.</returns>
public KeyInputDevice GetActiveKeyboard()
{
if (this.activeKeyboard == null)
{
SetActiveKeyboard("default keyboard");
}
return this.activeKeyboard;
}
/// <summary>
/// Gets the active wheel device for this Actions class.
/// </summary>
/// <returns>The active wheel device for this Actions class.</returns>
public WheelInputDevice GetActiveWheel()
{
if (this.activeWheel == null)
{
SetActiveWheel("default wheel");
}
return this.activeWheel;
}
/// <summary>
/// Sends a modifier key down message to the browser.
/// </summary>
/// <param name="theKey">The key to be sent.</param>
/// <returns>A self-reference to this <see cref="Actions"/>.</returns>
/// <exception cref="ArgumentException">If the key sent is not is not one
/// 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(string theKey)
{
return this.KeyDown(null, theKey);
}
/// <summary>
/// Sends a modifier key down message to the specified element in the browser.
/// </summary>
/// <param name="element">The element to which to send the key command.</param>
/// <param name="theKey">The key to be sent.</param>
/// <returns>A self-reference to this <see cref="Actions"/>.</returns>
/// <exception cref="ArgumentException">If the key sent is not is not one
/// 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)
{
if (string.IsNullOrEmpty(theKey))
{
throw new ArgumentException("The key value must not be null or empty", nameof(theKey));
}
ILocatable? target = GetLocatableFromElement(element);
if (element != null)
{
this.actionBuilder.AddAction(this.GetActivePointer().CreatePointerMove(element, 0, 0, duration));
this.actionBuilder.AddAction(this.GetActivePointer().CreatePointerDown(MouseButton.Left));
this.actionBuilder.AddAction(this.GetActivePointer().CreatePointerUp(MouseButton.Left));
}
this.actionBuilder.AddAction(this.GetActiveKeyboard().CreateKeyDown(theKey[0]));
this.actionBuilder.AddAction(new PauseInteraction(this.GetActiveKeyboard(), TimeSpan.FromMilliseconds(100)));
return this;
}
/// <summary>
/// Sends a modifier key up message to the browser.
/// </summary>
/// <param name="theKey">The key to be sent.</param>
/// <returns>A self-reference to this <see cref="Actions"/>.</returns>
/// <exception cref="ArgumentException">If the key sent is not is not one
/// 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(string theKey)
{
return this.KeyUp(null, theKey);
}
/// <summary>
/// Sends a modifier up down message to the specified element in the browser.
/// </summary>
/// <param name="element">The element to which to send the key command.</param>
/// <param name="theKey">The key to be sent.</param>
/// <returns>A self-reference to this <see cref="Actions"/>.</returns>
/// <exception cref="ArgumentException">If the key sent is not is not one
/// 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)
{
if (string.IsNullOrEmpty(theKey))
{
throw new ArgumentException("The key value must not be null or empty", nameof(theKey));
}
ILocatable? target = GetLocatableFromElement(element);
if (element != null)
{
this.actionBuilder.AddAction(this.GetActivePointer().CreatePointerMove(element, 0, 0, duration));
this.actionBuilder.AddAction(this.GetActivePointer().CreatePointerDown(MouseButton.Left));
this.actionBuilder.AddAction(this.GetActivePointer().CreatePointerUp(MouseButton.Left));
}
this.actionBuilder.AddAction(this.GetActiveKeyboard().CreateKeyUp(theKey[0]));
return this;
}
/// <summary>
/// Sends a sequence of keystrokes to the browser.
/// </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);
}
/// <summary>
/// Sends a sequence of keystrokes to the specified element in the browser.
/// </summary>
/// <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>
/// <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);
if (element != null)
{
this.actionBuilder.AddAction(this.GetActivePointer().CreatePointerMove(element, 0, 0, duration));
this.actionBuilder.AddAction(this.GetActivePointer().CreatePointerDown(MouseButton.Left));
this.actionBuilder.AddAction(this.GetActivePointer().CreatePointerUp(MouseButton.Left));
}
foreach (char key in keysToSend)
{
this.actionBuilder.AddAction(this.GetActiveKeyboard().CreateKeyDown(key));
this.actionBuilder.AddAction(this.GetActiveKeyboard().CreateKeyUp(key));
}
return this;
}
/// <summary>
/// Clicks and holds the mouse button down on the specified element.
/// </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();
return this;
}
/// <summary>
/// Clicks and holds the mouse button at the last known mouse coordinates.
/// </summary>
/// <returns>A self-reference to this <see cref="Actions"/>.</returns>
public Actions ClickAndHold()
{
this.actionBuilder.AddAction(this.GetActivePointer().CreatePointerDown(MouseButton.Left));
return this;
}
/// <summary>
/// Releases the mouse button on the specified element.
/// </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();
return this;
}
/// <summary>
/// Releases the mouse button at the last known mouse coordinates.
/// </summary>
/// <returns>A self-reference to this <see cref="Actions"/>.</returns>
public Actions Release()
{
this.actionBuilder.AddAction(this.GetActivePointer().CreatePointerUp(MouseButton.Left));
return this;
}
/// <summary>
/// Clicks the mouse on the specified element.
/// </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();
return this;
}
/// <summary>
/// Clicks the mouse at the last known mouse coordinates.
/// </summary>
/// <returns>A self-reference to this <see cref="Actions"/>.</returns>
public Actions Click()
{
this.actionBuilder.AddAction(this.GetActivePointer().CreatePointerDown(MouseButton.Left));
this.actionBuilder.AddAction(this.GetActivePointer().CreatePointerUp(MouseButton.Left));
return this;
}
/// <summary>
/// Double-clicks the mouse on the specified element.
/// </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();
return this;
}
/// <summary>
/// Double-clicks the mouse at the last known mouse coordinates.
/// </summary>
/// <returns>A self-reference to this <see cref="Actions"/>.</returns>
public Actions DoubleClick()
{
this.actionBuilder.AddAction(this.GetActivePointer().CreatePointerDown(MouseButton.Left));
this.actionBuilder.AddAction(this.GetActivePointer().CreatePointerUp(MouseButton.Left));
this.actionBuilder.AddAction(this.GetActivePointer().CreatePointerDown(MouseButton.Left));
this.actionBuilder.AddAction(this.GetActivePointer().CreatePointerUp(MouseButton.Left));
return this;
}
/// <summary>
/// Moves the mouse to the specified element.
/// </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)
{
throw new ArgumentException("MoveToElement cannot move to a null element with no offset.", nameof(toElement));
}
return this.MoveToElement(toElement, 0, 0);
}
/// <summary>
/// Moves the mouse to the specified offset of the top-left corner of the specified element.
/// In Selenium 4.3 the origin for the offset will be the in-view center point of the element.
/// </summary>
/// <param name="toElement">The element to which to move the mouse.</param>
/// <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>
public Actions MoveToElement(IWebElement toElement, int offsetX, int offsetY)
{
this.actionBuilder.AddAction(this.GetActivePointer().CreatePointerMove(toElement, offsetX, offsetY, duration));
return this;
}
/// <summary>
/// Moves the mouse to the specified offset of the last known mouse coordinates.
/// </summary>
/// <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>
public Actions MoveByOffset(int offsetX, int offsetY)
{
this.actionBuilder.AddAction(this.GetActivePointer().CreatePointerMove(CoordinateOrigin.Pointer, offsetX, offsetY, duration));
return this;
}
/// <summary>
/// Moves the mouse from the upper left corner of the current viewport by the provided offset.
/// </summary>
/// <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>
public Actions MoveToLocation(int offsetX, int offsetY)
{
this.actionBuilder.AddAction(this.GetActivePointer().CreatePointerMove(CoordinateOrigin.Viewport, offsetX, offsetY, duration));
return this;
}
/// <summary>
/// Right-clicks the mouse on the specified element.
/// </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();
return this;
}
/// <summary>
/// Right-clicks the mouse at the last known mouse coordinates.
/// </summary>
/// <returns>A self-reference to this <see cref="Actions"/>.</returns>
public Actions ContextClick()
{
this.actionBuilder.AddAction(this.GetActivePointer().CreatePointerDown(MouseButton.Right));
this.actionBuilder.AddAction(this.GetActivePointer().CreatePointerUp(MouseButton.Right));
return this;
}
/// <summary>
/// Performs a drag-and-drop operation from one element to another.
/// </summary>
/// <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);
return this;
}
/// <summary>
/// Performs a drag-and-drop operation on one element to a specified offset.
/// </summary>
/// <param name="source">The element on which the drag operation is started.</param>
/// <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();
return this;
}
/// <summary>
/// If the element is outside the viewport, scrolls the bottom of the element to the bottom of the viewport.
/// </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));
return this;
}
/// <summary>
/// Scrolls by provided amounts with the origin in the top left corner of the viewport.
/// </summary>
/// <param name="deltaX">Distance along X axis to scroll using the wheel. A negative value scrolls left.</param>
/// <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>
public Actions ScrollByAmount(int deltaX, int deltaY)
{
this.actionBuilder.AddAction(this.GetActiveWheel().CreateWheelScroll(deltaX, deltaY, duration));
return this;
}
/// <summary>
/// Scrolls by provided amount based on a provided origin.
/// </summary>
/// <remarks>
/// The scroll origin is either the center of an element or the upper left of the viewport plus any offsets.
/// If the origin is an element, and the element is not in the viewport, the bottom of the element will first
/// be scrolled to the bottom of the viewport.
/// </remarks>
/// <param name="scrollOrigin">Where scroll originates (viewport or element center) plus provided offsets.</param>
/// <param name="deltaX">Distance along X axis to scroll using the wheel. A negative value scrolls left.</param>
/// <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));
}
if (scrollOrigin.Viewport)
{
this.actionBuilder.AddAction(this.GetActiveWheel().CreateWheelScroll(CoordinateOrigin.Viewport,
scrollOrigin.XOffset, scrollOrigin.YOffset, deltaX, deltaY, duration));
}
else
{
this.actionBuilder.AddAction(this.GetActiveWheel().CreateWheelScroll(scrollOrigin.Element!,
scrollOrigin.XOffset, scrollOrigin.YOffset, deltaX, deltaY, duration));
}
return this;
}
/// <summary>
/// Performs a Pause.
/// </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));
return this;
}
/// <summary>
/// Builds the sequence of actions.
/// </summary>
/// <returns>A composite <see cref="IAction"/> which can be used to perform the actions.</returns>
public IAction Build()
{
return this;
}
/// <summary>
/// Performs the currently built action.
/// </summary>
public void Perform()
{
this.ActionExecutor.PerformActions(this.actionBuilder.ToActionSequenceList());
this.actionBuilder.ClearSequences();
}
/// <summary>
/// Clears the list of actions to be performed.
/// </summary>
public void Reset()
{
this.actionBuilder = new ActionBuilder();
}
/// <summary>
/// Gets the <see cref="ILocatable"/> instance of the specified <see cref="IWebElement"/>.
/// </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>
[return: NotNullIfNotNull(nameof(element))]
protected static ILocatable? GetLocatableFromElement(IWebElement? element)
{
if (element == null)
{
return null;
}
ILocatable? target = null;
IWrapsElement? wrapper = element as IWrapsElement;
while (wrapper != null)
{
target = wrapper.WrappedElement as ILocatable;
wrapper = wrapper.WrappedElement as IWrapsElement;
}
if (target == null)
{
target = element as ILocatable;
}
if (target == null)
{
throw new ArgumentException("The IWebElement object must implement or wrap an element that implements ILocatable.", nameof(element));
}
return target;
}
private static T? GetDriverAs<T>(IWebDriver? driver) where T : class
{
T? driverAsType = driver as T;
if (driverAsType == null)
{
IWrapsDriver? wrapper = driver as IWrapsDriver;
while (wrapper != null)
{
driverAsType = wrapper.WrappedDriver as T;
if (driverAsType != null)
{
driver = wrapper.WrappedDriver;
break;
}
wrapper = wrapper.WrappedDriver as IWrapsDriver;
}
}
return driverAsType;
}
}