blob: e208992d3acfcd0cf28373ae2d5e5b0187165fde [file] [log] [blame] [edit]
// <copyright file="WebDriver.cs" company="WebDriver 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;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Globalization;
using OpenQA.Selenium.Interactions;
using OpenQA.Selenium.Internal;
using OpenQA.Selenium.VirtualAuth;
using Microsoft.IdentityModel.Tokens;
namespace OpenQA.Selenium
{
public class WebDriver : IWebDriver, ISearchContext, IJavaScriptExecutor, IFindsElement, ITakesScreenshot, ISupportsPrint, IActionExecutor, IAllowsFileDetection, IHasCapabilities, IHasCommandExecutor, IHasSessionId, ICustomDriverCommandExecutor, IHasVirtualAuthenticator
{
/// <summary>
/// The default command timeout for HTTP requests in a RemoteWebDriver instance.
/// </summary>
protected static readonly TimeSpan DefaultCommandTimeout = TimeSpan.FromSeconds(60);
private ICommandExecutor executor;
private ICapabilities capabilities;
private IFileDetector fileDetector = new DefaultFileDetector();
private NetworkManager network;
private WebElementFactory elementFactory;
private SessionId sessionId;
private String authenticatorId;
private List<string> registeredCommands = new List<string>();
/// <summary>
/// Initializes a new instance of the <see cref="WebDriver"/> class.
/// </summary>
/// <param name="executor">The <see cref="ICommandExecutor"/> object used to execute commands.</param>
/// <param name="capabilities">The <see cref="ICapabilities"/> object used to configuer the driver session.</param>
protected WebDriver(ICommandExecutor executor, ICapabilities capabilities)
{
this.executor = executor;
this.StartSession(capabilities);
this.elementFactory = new WebElementFactory(this);
this.network = new NetworkManager(this);
this.registeredCommands.AddRange(DriverCommand.KnownCommands);
if ((this as ISupportsLogs) != null)
{
// Only add the legacy log commands if the driver supports
// retrieving the logs via the extension end points.
this.RegisterDriverCommand(DriverCommand.GetAvailableLogTypes, new HttpCommandInfo(HttpCommandInfo.GetCommand, "/session/{sessionId}/se/log/types"), true);
this.RegisterDriverCommand(DriverCommand.GetLog, new HttpCommandInfo(HttpCommandInfo.PostCommand, "/session/{sessionId}/se/log"), true);
}
}
/// <summary>
/// Gets the <see cref="ICommandExecutor"/> which executes commands for this driver.
/// </summary>
public ICommandExecutor CommandExecutor
{
get { return this.executor; }
}
/// <summary>
/// Gets the <see cref="ICapabilities"/> that the driver session was created with, which may be different from those requested.
/// </summary>
public ICapabilities Capabilities
{
get { return this.capabilities; }
}
/// <summary>
/// Gets or sets the URL the browser is currently displaying.
/// </summary>
/// <seealso cref="IWebDriver.Url"/>
/// <seealso cref="INavigation.GoToUrl(string)"/>
/// <seealso cref="INavigation.GoToUrl(System.Uri)"/>
public string Url
{
get
{
Response commandResponse = this.Execute(DriverCommand.GetCurrentUrl, null);
return commandResponse.Value.ToString();
}
set
{
if (value == null)
{
throw new ArgumentNullException(nameof(value), "Argument 'url' cannot be null.");
}
Dictionary<string, object> parameters = new Dictionary<string, object>();
parameters.Add("url", value);
this.Execute(DriverCommand.Get, parameters);
}
}
/// <summary>
/// Gets the title of the current browser window.
/// </summary>
public string Title
{
get
{
Response commandResponse = this.Execute(DriverCommand.GetTitle, null);
object returnedTitle = commandResponse != null ? commandResponse.Value : string.Empty;
return returnedTitle.ToString();
}
}
/// <summary>
/// Gets the source of the page last loaded by the browser.
/// </summary>
public string PageSource
{
get
{
string pageSource = string.Empty;
Response commandResponse = this.Execute(DriverCommand.GetPageSource, null);
pageSource = commandResponse.Value.ToString();
return pageSource;
}
}
/// <summary>
/// Gets the current window handle, which is an opaque handle to this
/// window that uniquely identifies it within this driver instance.
/// </summary>
public string CurrentWindowHandle
{
get
{
Response commandResponse = this.Execute(DriverCommand.GetCurrentWindowHandle, null);
return commandResponse.Value.ToString();
}
}
/// <summary>
/// Gets the window handles of open browser windows.
/// </summary>
public ReadOnlyCollection<string> WindowHandles
{
get
{
Response commandResponse = this.Execute(DriverCommand.GetWindowHandles, null);
object[] handles = (object[])commandResponse.Value;
List<string> handleList = new List<string>();
foreach (object handle in handles)
{
handleList.Add(handle.ToString());
}
return handleList.AsReadOnly();
}
}
/// <summary>
/// Gets a value indicating whether this object is a valid action executor.
/// </summary>
public bool IsActionExecutor
{
get { return true; }
}
/// <summary>
/// Gets the <see cref="SessionId"/> for the current session of this driver.
/// </summary>
public SessionId SessionId
{
get { return this.sessionId; }
}
/// <summary>
/// Gets or sets the <see cref="IFileDetector"/> responsible for detecting
/// sequences of keystrokes representing file paths and names.
/// </summary>
public virtual IFileDetector FileDetector
{
get
{
return this.fileDetector;
}
set
{
if (value == null)
{
throw new ArgumentNullException(nameof(value), "FileDetector cannot be null");
}
this.fileDetector = value;
}
}
internal INetwork Network
{
get { return this.network; }
}
/// <summary>
/// Gets or sets the factory object used to create instances of <see cref="WebElement"/>
/// or its subclasses.
/// </summary>
protected WebElementFactory ElementFactory
{
get { return this.elementFactory; }
set { this.elementFactory = value; }
}
/// <summary>
/// Closes the Browser
/// </summary>
public void Close()
{
this.Execute(DriverCommand.Close, null);
}
/// <summary>
/// Dispose the WebDriver Instance
/// </summary>
public void Dispose()
{
this.Dispose(true);
GC.SuppressFinalize(this);
}
/// <summary>
/// Executes JavaScript "asynchronously" in the context of the currently selected frame or window,
/// executing the callback function specified as the last argument in the list of arguments.
/// </summary>
/// <param name="script">The JavaScript code to execute.</param>
/// <param name="args">The arguments to the script.</param>
/// <returns>The value returned by the script.</returns>
public object ExecuteAsyncScript(string script, params object[] args)
{
return this.ExecuteScriptCommand(script, DriverCommand.ExecuteAsyncScript, args);
}
/// <summary>
/// Executes JavaScript in the context of the currently selected frame or window
/// </summary>
/// <param name="script">The JavaScript code to execute.</param>
/// <param name="args">The arguments to the script.</param>
/// <returns>The value returned by the script.</returns>
public object ExecuteScript(string script, params object[] args)
{
return this.ExecuteScriptCommand(script, DriverCommand.ExecuteScript, args);
}
/// <summary>
/// Executes JavaScript in the context of the currently selected frame or window
/// </summary>
/// <param name="script">A <see cref="PinnedScript"/> object containing the JavaScript code to execute.</param>
/// <param name="args">The arguments to the script.</param>
/// <returns>The value returned by the script.</returns>
public object ExecuteScript(PinnedScript script, params object[] args)
{
return this.ExecuteScript(script.ExecutionScript, args);
}
/// <summary>
/// Finds the first element in the page that matches the <see cref="By"/> object
/// </summary>
/// <param name="by">By mechanism to find the object</param>
/// <returns>IWebElement object so that you can interact with that object</returns>
/// <example>
/// <code>
/// IWebDriver driver = new InternetExplorerDriver();
/// IWebElement elem = driver.FindElement(By.Name("q"));
/// </code>
/// </example>
public IWebElement FindElement(By by)
{
if (by == null)
{
throw new ArgumentNullException(nameof(@by), "by cannot be null");
}
return by.FindElement(this);
}
/// <summary>
/// Finds an element matching the given mechanism and value.
/// </summary>
/// <param name="mechanism">The mechanism by which to find the element.</param>
/// <param name="value">The value to use to search for the element.</param>
/// <returns>The first <see cref="IWebElement"/> matching the given criteria.</returns>
public virtual IWebElement FindElement(string mechanism, string value)
{
Dictionary<string, object> parameters = new Dictionary<string, object>();
parameters.Add("using", mechanism);
parameters.Add("value", value);
Response commandResponse = this.Execute(DriverCommand.FindElement, parameters);
return this.GetElementFromResponse(commandResponse);
}
/// <summary>
/// Finds the elements on the page by using the <see cref="By"/> object and returns a ReadOnlyCollection of the Elements on the page
/// </summary>
/// <param name="by">By mechanism to find the element</param>
/// <returns>ReadOnlyCollection of IWebElement</returns>
/// <example>
/// <code>
/// IWebDriver driver = new InternetExplorerDriver();
/// ReadOnlyCollection<![CDATA[<IWebElement>]]> classList = driver.FindElements(By.ClassName("class"));
/// </code>
/// </example>
public ReadOnlyCollection<IWebElement> FindElements(By by)
{
if (by == null)
{
throw new ArgumentNullException(nameof(@by), "by cannot be null");
}
return by.FindElements(this);
}
/// <summary>
/// Finds all elements matching the given mechanism and value.
/// </summary>
/// <param name="mechanism">The mechanism by which to find the elements.</param>
/// <param name="value">The value to use to search for the elements.</param>
/// <returns>A collection of all of the <see cref="IWebElement">IWebElements</see> matching the given criteria.</returns>
public virtual ReadOnlyCollection<IWebElement> FindElements(string mechanism, string value)
{
Dictionary<string, object> parameters = new Dictionary<string, object>();
parameters.Add("using", mechanism);
parameters.Add("value", value);
Response commandResponse = this.Execute(DriverCommand.FindElements, parameters);
return this.GetElementsFromResponse(commandResponse);
}
/// <summary>
/// Gets a <see cref="Screenshot"/> object representing the image of the page on the screen.
/// </summary>
/// <returns>A <see cref="Screenshot"/> object containing the image.</returns>
public Screenshot GetScreenshot()
{
Response screenshotResponse = this.Execute(DriverCommand.Screenshot, null);
string base64 = screenshotResponse.Value.ToString();
return new Screenshot(base64);
}
/// <summary>
/// Gets a <see cref="PrintDocument"/> object representing a PDF-formatted print representation of the page.
/// </summary>
/// <param name="printOptions">A <see cref="PrintOptions"/> object describing the options of the printed document.</param>
/// <returns>The <see cref="PrintDocument"/> object containing the PDF-formatted print representation of the page.</returns>
public PrintDocument Print(PrintOptions printOptions)
{
Response commandResponse = this.Execute(DriverCommand.Print, printOptions.ToDictionary());
string base64 = commandResponse.Value.ToString();
return new PrintDocument(base64);
}
/// <summary>
/// Performs the specified list of actions with this action executor.
/// </summary>
/// <param name="actionSequenceList">The list of action sequences to perform.</param>
public void PerformActions(IList<ActionSequence> actionSequenceList)
{
if (actionSequenceList == null)
{
throw new ArgumentNullException(nameof(actionSequenceList), "List of action sequences must not be null");
}
List<object> objectList = new List<object>();
foreach (ActionSequence sequence in actionSequenceList)
{
objectList.Add(sequence.ToDictionary());
}
Dictionary<string, object> parameters = new Dictionary<string, object>();
parameters["actions"] = objectList;
this.Execute(DriverCommand.Actions, parameters);
}
/// <summary>
/// Resets the input state of the action executor.
/// </summary>
public void ResetInputState()
{
this.Execute(DriverCommand.CancelActions, null);
}
/// <summary>
/// Close the Browser and Dispose of WebDriver
/// </summary>
public void Quit()
{
this.Dispose();
}
/// <summary>
/// Method to give you access to switch frames and windows
/// </summary>
/// <returns>Returns an Object that allows you to Switch Frames and Windows</returns>
/// <example>
/// <code>
/// IWebDriver driver = new InternetExplorerDriver();
/// driver.SwitchTo().Frame("FrameName");
/// </code>
/// </example>
public ITargetLocator SwitchTo()
{
return new TargetLocator(this);
}
public IOptions Manage()
{
return new OptionsManager(this);
}
public INavigation Navigate()
{
return new Navigator(this);
}
/// <summary>
/// Executes a command with this driver.
/// </summary>
/// <param name="driverCommandToExecute">The name of the command to execute. The command name must be registered with the command executor, and must not be a command name known to this driver type.</param>
/// <param name="parameters">A <see cref="Dictionary{K, V}"/> containing the names and values of the parameters of the command.</param>
/// <returns>A <see cref="Response"/> containing information about the success or failure of the command and any data returned by the command.</returns>
public object ExecuteCustomDriverCommand(string driverCommandToExecute, Dictionary<string, object> parameters)
{
if (this.registeredCommands.Contains(driverCommandToExecute))
{
throw new WebDriverException(string.Format(CultureInfo.InvariantCulture, "A command named '{0}' is predefined by the driver class and cannot be executed with ExecuteCustomDriverCommand. It should be executed using a named method instead.", driverCommandToExecute));
}
return this.Execute(driverCommandToExecute, parameters).Value;
}
/// <summary>
/// Registers a set of commands to be executed with this driver instance.
/// </summary>
/// <param name="commands">An <see cref="IReadOnlyDictionary{string, CommandInfo}"/> where the keys are the names of the commands to register, and the values are the <see cref="CommandInfo"/> objects describing the commands.</param>
public void RegisterCustomDriverCommands(IReadOnlyDictionary<string, CommandInfo> commands)
{
foreach (KeyValuePair<string, CommandInfo> entry in commands)
{
this.RegisterCustomDriverCommand(entry.Key, entry.Value);
}
}
/// <summary>
/// Registers a command to be executed with this driver instance.
/// </summary>
/// <param name="commandName">The unique name of the command to register.</param>
/// <param name="commandInfo">The <see cref="CommandInfo"/> object describing the command.</param>
/// <returns><see langword="true"/> if the command was registered; otherwise, <see langword="false"/>.</returns>
public bool RegisterCustomDriverCommand(string commandName, CommandInfo commandInfo)
{
return this.RegisterDriverCommand(commandName, commandInfo, false);
}
/// <summary>
/// Registers a command to be executed with this driver instance.
/// </summary>
/// <param name="commandName">The unique name of the command to register.</param>
/// <param name="commandInfo">The <see cref="CommandInfo"/> object describing the command.</param>
/// <param name="isInternalCommand"><see langword="true"/> if the registered command is internal to the driver; otherwise <see langword="false"/>.</param>
/// <returns><see langword="true"/> if the command was registered; otherwise, <see langword="false"/>.</returns>
internal bool RegisterDriverCommand(string commandName, CommandInfo commandInfo, bool isInternalCommand)
{
bool commandAdded = this.CommandExecutor.TryAddCommand(commandName, commandInfo);
if (commandAdded && isInternalCommand)
{
this.registeredCommands.Add(commandName);
}
return commandAdded;
}
/// <summary>
/// Find the element in the response
/// </summary>
/// <param name="response">Response from the browser</param>
/// <returns>Element from the page</returns>
internal IWebElement GetElementFromResponse(Response response)
{
if (response == null)
{
throw new NoSuchElementException();
}
WebElement element = null;
Dictionary<string, object> elementDictionary = response.Value as Dictionary<string, object>;
if (elementDictionary != null)
{
element = this.elementFactory.CreateElement(elementDictionary);
}
return element;
}
/// <summary>
/// Finds the elements that are in the response
/// </summary>
/// <param name="response">Response from the browser</param>
/// <returns>Collection of elements</returns>
internal ReadOnlyCollection<IWebElement> GetElementsFromResponse(Response response)
{
List<IWebElement> toReturn = new List<IWebElement>();
object[] elements = response.Value as object[];
if (elements != null)
{
foreach (object elementObject in elements)
{
Dictionary<string, object> elementDictionary = elementObject as Dictionary<string, object>;
if (elementDictionary != null)
{
WebElement element = this.elementFactory.CreateElement(elementDictionary);
toReturn.Add(element);
}
}
}
return toReturn.AsReadOnly();
}
/// <summary>
/// Executes commands with the driver
/// </summary>
/// <param name="driverCommandToExecute">Command that needs executing</param>
/// <param name="parameters">Parameters needed for the command</param>
/// <returns>WebDriver Response</returns>
internal Response InternalExecute(string driverCommandToExecute, Dictionary<string, object> parameters)
{
return this.Execute(driverCommandToExecute, parameters);
}
/// <summary>
/// Executes a command with this driver .
/// </summary>
/// <param name="driverCommandToExecute">A <see cref="DriverCommand"/> value representing the command to execute.</param>
/// <param name="parameters">A <see cref="Dictionary{K, V}"/> containing the names and values of the parameters of the command.</param>
/// <returns>A <see cref="Response"/> containing information about the success or failure of the command and any data returned by the command.</returns>
protected virtual Response Execute(string driverCommandToExecute, Dictionary<string, object> parameters)
{
Command commandToExecute = new Command(this.sessionId, driverCommandToExecute, parameters);
Response commandResponse;
try
{
commandResponse = this.executor.Execute(commandToExecute);
}
catch (System.Net.Http.HttpRequestException e)
{
commandResponse = new Response
{
Status = WebDriverResult.UnhandledError,
Value = e
};
}
if (commandResponse.Status != WebDriverResult.Success)
{
UnpackAndThrowOnError(commandResponse, driverCommandToExecute);
}
return commandResponse;
}
/// <summary>
/// Starts a session with the driver
/// </summary>
/// <param name="desiredCapabilities">Capabilities of the browser</param>
protected void StartSession(ICapabilities desiredCapabilities)
{
Dictionary<string, object> parameters = new Dictionary<string, object>();
// If the object passed into the RemoteWebDriver constructor is a
// RemoteSessionSettings object, it is expected that all intermediate
// and end nodes are compliant with the W3C WebDriver Specification,
// and therefore will already contain all of the appropriate values
// for establishing a session.
RemoteSessionSettings remoteSettings = desiredCapabilities as RemoteSessionSettings;
if (remoteSettings == null)
{
Dictionary<string, object> matchCapabilities = this.GetCapabilitiesDictionary(desiredCapabilities);
List<object> firstMatchCapabilitiesList = new List<object>();
firstMatchCapabilitiesList.Add(matchCapabilities);
Dictionary<string, object> specCompliantCapabilitiesDictionary = new Dictionary<string, object>();
specCompliantCapabilitiesDictionary["firstMatch"] = firstMatchCapabilitiesList;
parameters.Add("capabilities", specCompliantCapabilitiesDictionary);
}
else
{
parameters.Add("capabilities", remoteSettings.ToDictionary());
}
Response response = this.Execute(DriverCommand.NewSession, parameters);
Dictionary<string, object> rawCapabilities = response.Value as Dictionary<string, object>;
if (rawCapabilities == null)
{
string errorMessage = string.Format(CultureInfo.InvariantCulture, "The new session command returned a value ('{0}') that is not a valid JSON object.", response.Value);
throw new WebDriverException(errorMessage);
}
ReturnedCapabilities returnedCapabilities = new ReturnedCapabilities(rawCapabilities);
this.capabilities = returnedCapabilities;
this.sessionId = new SessionId(response.SessionId);
}
/// <summary>
/// Gets the capabilities as a dictionary.
/// </summary>
/// <param name="capabilitiesToConvert">The dictionary to return.</param>
/// <returns>A Dictionary consisting of the capabilities requested.</returns>
/// <remarks>This method is only transitional. Do not rely on it. It will be removed
/// once browser driver capability formats stabilize.</remarks>
protected virtual Dictionary<string, object> GetCapabilitiesDictionary(ICapabilities capabilitiesToConvert)
{
Dictionary<string, object> capabilitiesDictionary = new Dictionary<string, object>();
IHasCapabilitiesDictionary capabilitiesObject = capabilitiesToConvert as IHasCapabilitiesDictionary;
foreach (KeyValuePair<string, object> entry in capabilitiesObject.CapabilitiesDictionary)
{
if (CapabilityType.IsSpecCompliantCapabilityName(entry.Key))
{
capabilitiesDictionary.Add(entry.Key, entry.Value);
}
}
return capabilitiesDictionary;
}
/// <summary>
/// Registers a command to be executed with this driver instance as an internally known driver command.
/// </summary>
/// <param name="commandName">The unique name of the command to register.</param>
/// <param name="commandInfo">The <see cref="CommandInfo"/> object describing the command.</param>
/// <returns><see langword="true"/> if the command was registered; otherwise, <see langword="false"/>.</returns>
protected bool RegisterInternalDriverCommand(string commandName, CommandInfo commandInfo)
{
return this.RegisterDriverCommand(commandName, commandInfo, true);
}
/// <summary>
/// Stops the client from running
/// </summary>
/// <param name="disposing">if its in the process of disposing</param>
protected virtual void Dispose(bool disposing)
{
try
{
this.Execute(DriverCommand.Quit, null);
}
catch (NotImplementedException)
{
}
catch (InvalidOperationException)
{
}
catch (WebDriverException)
{
}
finally
{
this.sessionId = null;
}
this.executor.Dispose();
}
private static void UnpackAndThrowOnError(Response errorResponse, string commandToExecute)
{
// Check the status code of the error, and only handle if not success.
if (errorResponse.Status != WebDriverResult.Success)
{
Dictionary<string, object> errorAsDictionary = errorResponse.Value as Dictionary<string, object>;
if (errorAsDictionary != null)
{
ErrorResponse errorResponseObject = new ErrorResponse(errorAsDictionary);
string errorMessage = errorResponseObject.Message;
switch (errorResponse.Status)
{
case WebDriverResult.NoSuchElement:
throw new NoSuchElementException(errorMessage);
case WebDriverResult.NoSuchFrame:
throw new NoSuchFrameException(errorMessage);
case WebDriverResult.UnknownCommand:
throw new NotImplementedException(errorMessage);
case WebDriverResult.ObsoleteElement:
throw new StaleElementReferenceException(errorMessage);
case WebDriverResult.ElementClickIntercepted:
throw new ElementClickInterceptedException(errorMessage);
case WebDriverResult.ElementNotInteractable:
throw new ElementNotInteractableException(errorMessage);
case WebDriverResult.ElementNotDisplayed:
throw new ElementNotVisibleException(errorMessage);
case WebDriverResult.InvalidElementState:
case WebDriverResult.ElementNotSelectable:
throw new InvalidElementStateException(errorMessage);
case WebDriverResult.UnhandledError:
throw new WebDriverException(errorMessage);
case WebDriverResult.NoSuchDocument:
throw new NoSuchElementException(errorMessage);
case WebDriverResult.Timeout:
throw new WebDriverTimeoutException(errorMessage);
case WebDriverResult.NoSuchWindow:
throw new NoSuchWindowException(errorMessage);
case WebDriverResult.InvalidCookieDomain:
throw new InvalidCookieDomainException(errorMessage);
case WebDriverResult.UnableToSetCookie:
throw new UnableToSetCookieException(errorMessage);
case WebDriverResult.AsyncScriptTimeout:
throw new WebDriverTimeoutException(errorMessage);
case WebDriverResult.UnexpectedAlertOpen:
// TODO(JimEvans): Handle the case where the unexpected alert setting
// has been set to "ignore", so there is still a valid alert to be
// handled.
string alertText = string.Empty;
if (errorAsDictionary.ContainsKey("alert"))
{
Dictionary<string, object> alertDescription = errorAsDictionary["alert"] as Dictionary<string, object>;
if (alertDescription != null && alertDescription.ContainsKey("text"))
{
alertText = alertDescription["text"].ToString();
}
}
else if (errorAsDictionary.ContainsKey("data"))
{
Dictionary<string, object> alertData = errorAsDictionary["data"] as Dictionary<string, object>;
if (alertData != null && alertData.ContainsKey("text"))
{
alertText = alertData["text"].ToString();
}
}
throw new UnhandledAlertException(errorMessage, alertText);
case WebDriverResult.NoAlertPresent:
throw new NoAlertPresentException(errorMessage);
case WebDriverResult.InvalidSelector:
throw new InvalidSelectorException(errorMessage);
case WebDriverResult.NoSuchDriver:
throw new WebDriverException(errorMessage);
case WebDriverResult.InvalidArgument:
throw new WebDriverArgumentException(errorMessage);
case WebDriverResult.UnexpectedJavaScriptError:
throw new JavaScriptException(errorMessage);
case WebDriverResult.MoveTargetOutOfBounds:
throw new MoveTargetOutOfBoundsException(errorMessage);
case WebDriverResult.NoSuchShadowRoot:
throw new NoSuchShadowRootException(errorMessage);
default:
throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, "{0} ({1})", errorMessage, errorResponse.Status));
}
}
else
{
throw new WebDriverException("The " + commandToExecute + " command returned an unexpected error. " + errorResponse.Value.ToString());
}
}
}
/// <summary>
/// Executes JavaScript in the context of the currently selected frame or window using a specific command.
/// </summary>
/// <param name="script">The JavaScript code to execute.</param>
/// <param name="commandName">The name of the command to execute.</param>
/// <param name="args">The arguments to the script.</param>
/// <returns>The value returned by the script.</returns>
protected object ExecuteScriptCommand(string script, string commandName, params object[] args)
{
object[] convertedArgs = ConvertArgumentsToJavaScriptObjects(args);
Dictionary<string, object> parameters = new Dictionary<string, object>();
parameters.Add("script", script);
if (convertedArgs != null && convertedArgs.Length > 0)
{
parameters.Add("args", convertedArgs);
}
else
{
parameters.Add("args", new object[] { });
}
Response commandResponse = this.Execute(commandName, parameters);
return this.ParseJavaScriptReturnValue(commandResponse.Value);
}
private static object ConvertObjectToJavaScriptObject(object arg)
{
IWrapsElement argAsWrapsElement = arg as IWrapsElement;
IWebDriverObjectReference argAsObjectReference = arg as IWebDriverObjectReference;
IEnumerable argAsEnumerable = arg as IEnumerable;
IDictionary argAsDictionary = arg as IDictionary;
if (argAsObjectReference == null && argAsWrapsElement != null)
{
argAsObjectReference = argAsWrapsElement.WrappedElement as IWebDriverObjectReference;
}
object converted = null;
if (arg is string || arg is float || arg is double || arg is int || arg is long || arg is bool || arg == null)
{
converted = arg;
}
else if (argAsObjectReference != null)
{
Dictionary<string, object> webDriverObjectReferenceDictionary = argAsObjectReference.ToDictionary();
converted = webDriverObjectReferenceDictionary;
}
else if (argAsDictionary != null)
{
// Note that we must check for the argument being a dictionary before
// checking for IEnumerable, since dictionaries also implement IEnumerable.
// Additionally, JavaScript objects have property names as strings, so all
// keys will be converted to strings.
Dictionary<string, object> dictionary = new Dictionary<string, object>();
foreach (var key in argAsDictionary.Keys)
{
dictionary.Add(key.ToString(), ConvertObjectToJavaScriptObject(argAsDictionary[key]));
}
converted = dictionary;
}
else if (argAsEnumerable != null)
{
List<object> objectList = new List<object>();
foreach (object item in argAsEnumerable)
{
objectList.Add(ConvertObjectToJavaScriptObject(item));
}
converted = objectList.ToArray();
}
else
{
throw new ArgumentException("Argument is of an illegal type" + arg.ToString(), nameof(arg));
}
return converted;
}
/// <summary>
/// Converts the arguments to JavaScript objects.
/// </summary>
/// <param name="args">The arguments.</param>
/// <returns>The list of the arguments converted to JavaScript objects.</returns>
private static object[] ConvertArgumentsToJavaScriptObjects(object[] args)
{
if (args == null)
{
return new object[] { null };
}
for (int i = 0; i < args.Length; i++)
{
args[i] = ConvertObjectToJavaScriptObject(args[i]);
}
return args;
}
private object ParseJavaScriptReturnValue(object responseValue)
{
object returnValue = null;
Dictionary<string, object> resultAsDictionary = responseValue as Dictionary<string, object>;
object[] resultAsArray = responseValue as object[];
if (resultAsDictionary != null)
{
if (this.elementFactory.ContainsElementReference(resultAsDictionary))
{
returnValue = this.elementFactory.CreateElement(resultAsDictionary);
}
else if (ShadowRoot.ContainsShadowRootReference(resultAsDictionary))
{
returnValue = ShadowRoot.FromDictionary(this, resultAsDictionary);
}
else
{
// Recurse through the dictionary, re-parsing each value.
string[] keyCopy = new string[resultAsDictionary.Keys.Count];
resultAsDictionary.Keys.CopyTo(keyCopy, 0);
foreach (string key in keyCopy)
{
resultAsDictionary[key] = this.ParseJavaScriptReturnValue(resultAsDictionary[key]);
}
returnValue = resultAsDictionary;
}
}
else if (resultAsArray != null)
{
bool allElementsAreWebElements = true;
List<object> toReturn = new List<object>();
foreach (object item in resultAsArray)
{
object parsedItem = this.ParseJavaScriptReturnValue(item);
IWebElement parsedItemAsElement = parsedItem as IWebElement;
if (parsedItemAsElement == null)
{
allElementsAreWebElements = false;
}
toReturn.Add(parsedItem);
}
if (toReturn.Count > 0 && allElementsAreWebElements)
{
List<IWebElement> elementList = new List<IWebElement>();
foreach (object listItem in toReturn)
{
IWebElement itemAsElement = listItem as IWebElement;
elementList.Add(itemAsElement);
}
returnValue = elementList.AsReadOnly();
}
else
{
returnValue = toReturn.AsReadOnly();
}
}
else
{
returnValue = responseValue;
}
return returnValue;
}
/// <summary>
/// Creates a Virtual Authenticator.
/// </summary>
/// <param name="options"> VirtualAuthenticator Options (https://w3c.github.io/webauthn/#sctn-automation-virtual-authenticators)</param>
/// <returns> Authenticator id as string </returns>
public string AddVirtualAuthenticator(VirtualAuthenticatorOptions options)
{
Response commandResponse = this.Execute(DriverCommand.AddVirtualAuthenticator, options.ToDictionary());
string id = commandResponse.Value.ToString();
this.authenticatorId = id;
return this.authenticatorId;
}
/// <summary>
/// Removes the Virtual Authenticator
/// </summary>
/// <param name="authenticatorId"> Id as string that uniquely identifies a Virtual Authenticator</param>
public void RemoveVirtualAuthenticator(string authenticatorId)
{
Dictionary<string, object> parameters = new Dictionary<string, object>();
parameters.Add("authenticatorId", this.authenticatorId);
this.Execute(DriverCommand.RemoveVirtualAuthenticator, parameters);
this.authenticatorId = null;
}
public string AuthenticatorId { get; }
/// <summary>
/// Add a credential to the Virtual Authenticator/
/// </summary>
/// <param name="credential"> The credential to be stored in the Virtual Authenticator</param>
public void AddCredential(Credential credential)
{
Dictionary<string, object> parameters = new Dictionary<string, object>(credential.ToDictionary());
parameters.Add("authenticatorId", this.authenticatorId);
this.Execute(driverCommandToExecute: DriverCommand.AddCredential, parameters);
}
/// <summary>
/// Retrieves all the credentials stored in the Virtual Authenticator
/// </summary>
/// <returns> List of credentials </returns>
public List<Credential> GetCredentials()
{
Dictionary<string, object> parameters = new Dictionary<string, object>();
parameters.Add("authenticatorId", this.authenticatorId);
object[] commandResponse = (object[])this.Execute(driverCommandToExecute: DriverCommand.GetCredentials, parameters).Value;
List<Credential> credentials = new List<Credential>();
foreach (object dictionary in commandResponse)
{
Credential credential = Credential.FromDictionary((Dictionary<string, object>)dictionary);
credentials.Add(credential);
}
return credentials;
}
/// <summary>
/// Removes the credential identified by the credentialId from the Virtual Authenticator.
/// </summary>
/// <param name="credentialId"> The id as byte array that uniquely identifies a credential </param>
public void RemoveCredential(byte[] credentialId)
{
RemoveCredential(Base64UrlEncoder.Encode(credentialId));
}
/// <summary>
/// Removes the credential identified by the credentialId from the Virtual Authenticator.
/// </summary>
/// <param name="credentialId"> The id as string that uniquely identifies a credential </param>
public void RemoveCredential(string credentialId)
{
Dictionary<string, object> parameters = new Dictionary<string, object>();
parameters.Add("authenticatorId", this.authenticatorId);
parameters.Add("credentialId", credentialId);
this.Execute(driverCommandToExecute: DriverCommand.RemoveCredential, parameters);
}
/// <summary>
/// Removes all the credentials stored in the Virtual Authenticator.
/// </summary>
public void RemoveAllCredentials()
{
Dictionary<string, object> parameters = new Dictionary<string, object>();
parameters.Add("authenticatorId", this.authenticatorId);
this.Execute(driverCommandToExecute: DriverCommand.RemoveAllCredentials, parameters);
}
/// <summary>
/// Sets the isUserVerified property for the Virtual Authenticator.
/// </summary>
/// <param name="verified">The boolean value representing value to be set </param>
public void SetUserVerified(bool verified)
{
Dictionary<string, object> parameters = new Dictionary<string, object>();
parameters.Add("authenticatorId", this.authenticatorId);
parameters.Add("isUserVerified", verified);
this.Execute(driverCommandToExecute: DriverCommand.SetUserVerified, parameters);
}
}
}