-
-
Save spinylobster/d3683002ef96b0ee52dc59ca385ee2c0 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// <copyright file="ChromeDriver.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.Generic; | |
using System.Collections.ObjectModel; | |
using OpenQA.Selenium.Remote; | |
using OpenQA.Selenium.Chromium; | |
namespace OpenQA.Selenium.Chrome | |
{ | |
/// <summary> | |
/// Provides a mechanism to write tests against Chrome | |
/// </summary> | |
/// <example> | |
/// <code> | |
/// [TestFixture] | |
/// public class Testing | |
/// { | |
/// private IWebDriver driver; | |
/// <para></para> | |
/// [SetUp] | |
/// public void SetUp() | |
/// { | |
/// driver = new ChromeDriver(); | |
/// } | |
/// <para></para> | |
/// [Test] | |
/// public void TestGoogle() | |
/// { | |
/// driver.Navigate().GoToUrl("http://www.google.co.uk"); | |
/// /* | |
/// * Rest of the test | |
/// */ | |
/// } | |
/// <para></para> | |
/// [TearDown] | |
/// public void TearDown() | |
/// { | |
/// driver.Quit(); | |
/// } | |
/// } | |
/// </code> | |
/// </example> | |
public class MyChromeDriver : MyChromiumDriver | |
{ | |
private static Dictionary<string, CommandInfo> chromeCustomCommands = new Dictionary<string, CommandInfo>() | |
{ | |
{ ExecuteCdp, new HttpCommandInfo(HttpCommandInfo.PostCommand, "/session/{sessionId}/goog/cdp/execute") }, | |
{ GetCastSinksCommand, new HttpCommandInfo(HttpCommandInfo.GetCommand, "/session/{sessionId}/goog/cast/get_sinks") }, | |
{ SelectCastSinkCommand, new HttpCommandInfo(HttpCommandInfo.PostCommand, "/session/{sessionId}/goog/cast/set_sink_to_use") }, | |
{ StartCastTabMirroringCommand, new HttpCommandInfo(HttpCommandInfo.PostCommand, "/session/{sessionId}/goog/cast/start_tab_mirroring") }, | |
{ StartCastDesktopMirroringCommand, new HttpCommandInfo(HttpCommandInfo.PostCommand, "/session/{sessionId}/goog/cast/start_desktop_mirroring") }, | |
{ GetCastIssueMessageCommand, new HttpCommandInfo(HttpCommandInfo.GetCommand, "/session/{sessionId}/goog/cast/get_issue_message") }, | |
{ StopCastingCommand, new HttpCommandInfo(HttpCommandInfo.PostCommand, "/session/{sessionId}/goog/cast/stop_casting") } | |
}; | |
/// <summary> | |
/// Initializes a new instance of the <see cref="ChromeDriver"/> class. | |
/// </summary> | |
public MyChromeDriver() | |
: this(new ChromeOptions()) | |
{ | |
} | |
/// <summary> | |
/// Initializes a new instance of the <see cref="ChromeDriver"/> class using the specified options. | |
/// </summary> | |
/// <param name="options">The <see cref="ChromeOptions"/> to be used with the Chrome driver.</param> | |
public MyChromeDriver(ChromeOptions options) | |
: this(ChromeDriverService.CreateDefaultService(), options, RemoteWebDriver.DefaultCommandTimeout) | |
{ | |
} | |
/// <summary> | |
/// Initializes a new instance of the <see cref="ChromeDriver"/> class using the specified driver service. | |
/// </summary> | |
/// <param name="service">The <see cref="ChromeDriverService"/> used to initialize the driver.</param> | |
public MyChromeDriver(ChromeDriverService service) | |
: this(service, new ChromeOptions()) | |
{ | |
} | |
/// <summary> | |
/// Initializes a new instance of the <see cref="ChromeDriver"/> class using the specified path | |
/// to the directory containing ChromeDriver.exe. | |
/// </summary> | |
/// <param name="chromeDriverDirectory">The full path to the directory containing ChromeDriver.exe.</param> | |
public MyChromeDriver(string chromeDriverDirectory) | |
: this(chromeDriverDirectory, new ChromeOptions()) | |
{ | |
} | |
/// <summary> | |
/// Initializes a new instance of the <see cref="ChromeDriver"/> class using the specified path | |
/// to the directory containing ChromeDriver.exe and options. | |
/// </summary> | |
/// <param name="chromeDriverDirectory">The full path to the directory containing ChromeDriver.exe.</param> | |
/// <param name="options">The <see cref="ChromeOptions"/> to be used with the Chrome driver.</param> | |
public MyChromeDriver(string chromeDriverDirectory, ChromeOptions options) | |
: this(chromeDriverDirectory, options, RemoteWebDriver.DefaultCommandTimeout) | |
{ | |
} | |
/// <summary> | |
/// Initializes a new instance of the <see cref="ChromeDriver"/> class using the specified path | |
/// to the directory containing ChromeDriver.exe, options, and command timeout. | |
/// </summary> | |
/// <param name="chromeDriverDirectory">The full path to the directory containing ChromeDriver.exe.</param> | |
/// <param name="options">The <see cref="ChromeOptions"/> to be used with the Chrome driver.</param> | |
/// <param name="commandTimeout">The maximum amount of time to wait for each command.</param> | |
public MyChromeDriver(string chromeDriverDirectory, ChromeOptions options, TimeSpan commandTimeout) | |
: this(ChromeDriverService.CreateDefaultService(chromeDriverDirectory), options, commandTimeout) | |
{ | |
} | |
/// <summary> | |
/// Initializes a new instance of the <see cref="ChromeDriver"/> class using the specified | |
/// <see cref="ChromeDriverService"/> and options. | |
/// </summary> | |
/// <param name="service">The <see cref="ChromeDriverService"/> to use.</param> | |
/// <param name="options">The <see cref="ChromeOptions"/> used to initialize the driver.</param> | |
public MyChromeDriver(ChromeDriverService service, ChromeOptions options) | |
: this(service, options, RemoteWebDriver.DefaultCommandTimeout) | |
{ | |
} | |
/// <summary> | |
/// Initializes a new instance of the <see cref="ChromeDriver"/> class using the specified <see cref="ChromeDriverService"/>. | |
/// </summary> | |
/// <param name="service">The <see cref="ChromeDriverService"/> to use.</param> | |
/// <param name="options">The <see cref="ChromeOptions"/> to be used with the Chrome driver.</param> | |
/// <param name="commandTimeout">The maximum amount of time to wait for each command.</param> | |
public MyChromeDriver(ChromeDriverService service, ChromeOptions options, TimeSpan commandTimeout) | |
: base(service, options, commandTimeout) | |
{ | |
this.AddCustomChromeCommands(); | |
} | |
/// <summary> | |
/// Gets a read-only dictionary of the custom WebDriver commands defined for ChromeDriver. | |
/// The keys of the dictionary are the names assigned to the command; the values are the | |
/// <see cref="CommandInfo"/> objects describing the command behavior. | |
/// </summary> | |
public static IReadOnlyDictionary<string, CommandInfo> CustomCommandDefinitions | |
{ | |
get | |
{ | |
Dictionary<string, CommandInfo> customCommands = new Dictionary<string, CommandInfo>(); | |
foreach (KeyValuePair<string, CommandInfo> entry in ChromiumCustomCommands) | |
{ | |
customCommands[entry.Key] = entry.Value; | |
} | |
foreach (KeyValuePair<string, CommandInfo> entry in chromeCustomCommands) | |
{ | |
customCommands[entry.Key] = entry.Value; | |
} | |
return new ReadOnlyDictionary<string, CommandInfo>(customCommands); | |
} | |
} | |
private void AddCustomChromeCommands() | |
{ | |
foreach (KeyValuePair<string, CommandInfo> entry in CustomCommandDefinitions) | |
{ | |
this.RegisterInternalDriverCommand(entry.Key, entry.Value); | |
} | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// <copyright file="ChromiumDriver.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.Generic; | |
using System.Collections.ObjectModel; | |
using OpenQA.Selenium.DevTools; | |
using OpenQA.Selenium.Remote; | |
namespace OpenQA.Selenium.Chromium | |
{ | |
/// <summary> | |
/// Provides an abstract way to access Chromium-based browsers to run tests. | |
/// </summary> | |
public class MyChromiumDriver : WebDriver, ISupportsLogs, IDevTools | |
{ | |
/// <summary> | |
/// Accept untrusted SSL Certificates | |
/// </summary> | |
public static readonly bool AcceptUntrustedCertificates = true; | |
/// <summary> | |
/// Command for executing a Chrome DevTools Protocol command in a driver for a Chromium-based browser. | |
/// </summary> | |
public static readonly string ExecuteCdp = "executeCdpCommand"; | |
/// <summary> | |
/// Command for getting cast sinks in a driver for a Chromium-based browser. | |
/// </summary> | |
public static readonly string GetCastSinksCommand = "getCastSinks"; | |
/// <summary> | |
/// Command for selecting a cast sink in a driver for a Chromium-based browser. | |
/// </summary> | |
public static readonly string SelectCastSinkCommand = "selectCastSink"; | |
/// <summary> | |
/// Command for starting cast tab mirroring in a driver for a Chromium-based browser. | |
/// </summary> | |
public static readonly string StartCastTabMirroringCommand = "startCastTabMirroring"; | |
/// <summary> | |
/// Command for starting cast desktop mirroring in a driver for a Chromium-based browser. | |
/// </summary> | |
public static readonly string StartCastDesktopMirroringCommand = "startCastDesktopMirroring"; | |
/// <summary> | |
/// Command for getting a cast issued message in a driver for a Chromium-based browser. | |
/// </summary> | |
public static readonly string GetCastIssueMessageCommand = "getCastIssueMessage"; | |
/// <summary> | |
/// Command for stopping casting in a driver for a Chromium-based browser. | |
/// </summary> | |
public static readonly string StopCastingCommand = "stopCasting"; | |
/// <summary> | |
/// Command for getting the simulated network conditions in a driver for a Chromium-based browser. | |
/// </summary> | |
public static readonly string GetNetworkConditionsCommand = "getNetworkConditions"; | |
/// <summary> | |
/// Command for setting the simulated network conditions in a driver for a Chromium-based browser. | |
/// </summary> | |
public static readonly string SetNetworkConditionsCommand = "setNetworkConditions"; | |
/// <summary> | |
/// Command for deleting the simulated network conditions in a driver for a Chromium-based browser. | |
/// </summary> | |
public static readonly string DeleteNetworkConditionsCommand = "deleteNetworkConditions"; | |
/// <summary> | |
/// Command for executing a Chrome DevTools Protocol command in a driver for a Chromium-based browser. | |
/// </summary> | |
public static readonly string SendChromeCommand = "sendChromeCommand"; | |
/// <summary> | |
/// Command for executing a Chrome DevTools Protocol command that returns a result in a driver for a Chromium-based browser. | |
/// </summary> | |
public static readonly string SendChromeCommandWithResult = "sendChromeCommandWithResult"; | |
/// <summary> | |
/// Command for launching an app in a driver for a Chromium-based browser. | |
/// </summary> | |
public static readonly string LaunchAppCommand = "launchAppCommand"; | |
/// <summary> | |
/// Command for setting permissions in a driver for a Chromium-based browser. | |
/// </summary> | |
public static readonly string SetPermissionCommand = "setPermission"; | |
private readonly string optionsCapabilityName; | |
private DevToolsSession devToolsSession; | |
private static Dictionary<string, CommandInfo> chromiumCustomCommands = new Dictionary<string, CommandInfo>() | |
{ | |
{ GetNetworkConditionsCommand, new HttpCommandInfo(HttpCommandInfo.GetCommand, "/session/{sessionId}/chromium/network_conditions") }, | |
{ SetNetworkConditionsCommand, new HttpCommandInfo(HttpCommandInfo.PostCommand, "/session/{sessionId}/chromium/network_conditions") }, | |
{ DeleteNetworkConditionsCommand, new HttpCommandInfo(HttpCommandInfo.DeleteCommand, "/session/{sessionId}/chromium/network_conditions") }, | |
{ SendChromeCommand, new HttpCommandInfo(HttpCommandInfo.PostCommand, "/session/{sessionId}/chromium/send_command") }, | |
{ SendChromeCommandWithResult, new HttpCommandInfo(HttpCommandInfo.PostCommand, "/session/{sessionId}/chromium/send_command_and_get_result") }, | |
{ LaunchAppCommand, new HttpCommandInfo(HttpCommandInfo.PostCommand, "/session/{sessionId}/chromium/launch_app") }, | |
{ SetPermissionCommand, new HttpCommandInfo(HttpCommandInfo.PostCommand, "/session/{sessionId}/permissions") } | |
}; | |
/// <summary> | |
/// Initializes a new instance of the <see cref="ChromiumDriver"/> class using the specified <see cref="ChromiumDriverService"/>. | |
/// </summary> | |
/// <param name="service">The <see cref="ChromiumDriverService"/> to use.</param> | |
/// <param name="options">The <see cref="ChromiumOptions"/> to be used with the ChromiumDriver.</param> | |
/// <param name="commandTimeout">The maximum amount of time to wait for each command.</param> | |
protected MyChromiumDriver(ChromiumDriverService service, ChromiumOptions options, TimeSpan commandTimeout) | |
: base(new MyDriverServiceCommandExecutor(service, commandTimeout), ConvertOptionsToCapabilities(options)) | |
{ | |
this.optionsCapabilityName = options.CapabilityName; | |
} | |
protected static IReadOnlyDictionary<string, CommandInfo> ChromiumCustomCommands | |
{ | |
get { return new ReadOnlyDictionary<string, CommandInfo>(chromiumCustomCommands); } | |
} | |
/// <summary> | |
/// Gets or sets the <see cref="IFileDetector"/> responsible for detecting | |
/// sequences of keystrokes representing file paths and names. | |
/// </summary> | |
/// <remarks>The Chromium driver does not allow a file detector to be set, | |
/// as the server component of the Chromium driver only | |
/// allows uploads from the local computer environment. Attempting to set | |
/// this property has no effect, but does not throw an exception. If you | |
/// are attempting to run the Chromium driver remotely, use <see cref="RemoteWebDriver"/> | |
/// in conjunction with a standalone WebDriver server.</remarks> | |
public override IFileDetector FileDetector | |
{ | |
get { return base.FileDetector; } | |
set { } | |
} | |
/// <summary> | |
/// Gets a value indicating whether a DevTools session is active. | |
/// </summary> | |
public bool HasActiveDevToolsSession | |
{ | |
get { return this.devToolsSession != null; } | |
} | |
/// <summary> | |
/// Gets or sets the network condition emulation for Chromium. | |
/// </summary> | |
public ChromiumNetworkConditions NetworkConditions | |
{ | |
get | |
{ | |
Response response = this.Execute(GetNetworkConditionsCommand, null); | |
return ChromiumNetworkConditions.FromDictionary(response.Value as Dictionary<string, object>); | |
} | |
set | |
{ | |
if (value == null) | |
{ | |
throw new ArgumentNullException(nameof(value), "value must not be null"); | |
} | |
Dictionary<string, object> parameters = new Dictionary<string, object>(); | |
parameters["network_conditions"] = value; | |
this.Execute(SetNetworkConditionsCommand, parameters); | |
} | |
} | |
/// <summary> | |
/// Launches a Chromium based application. | |
/// </summary> | |
/// <param name="id">ID of the chromium app to launch.</param> | |
public void LaunchApp(string id) | |
{ | |
if (id == null) | |
{ | |
throw new ArgumentNullException(nameof(id), "id must not be null"); | |
} | |
Dictionary<string, object> parameters = new Dictionary<string, object>(); | |
parameters["id"] = id; | |
this.Execute(LaunchAppCommand, parameters); | |
} | |
/// <summary> | |
/// Set supported permission on browser. | |
/// </summary> | |
/// <param name="permissionName">Name of item to set the permission on.</param> | |
/// <param name="permissionValue">Value to set the permission to.</param> | |
public void SetPermission(string permissionName, string permissionValue) | |
{ | |
if (permissionName == null) | |
{ | |
throw new ArgumentNullException(nameof(permissionName), "name must not be null"); | |
} | |
if (permissionValue == null) | |
{ | |
throw new ArgumentNullException(nameof(permissionValue), "value must not be null"); | |
} | |
Dictionary<string, object> nameParameter = new Dictionary<string, object>(); | |
nameParameter["name"] = permissionName; | |
Dictionary<string, object> parameters = new Dictionary<string, object>(); | |
parameters["descriptor"] = nameParameter; | |
parameters["state"] = permissionValue; | |
this.Execute(SetPermissionCommand, parameters); | |
} | |
/// <summary> | |
/// Executes a custom Chrome Dev Tools Protocol Command. | |
/// </summary> | |
/// <param name="commandName">Name of the command to execute.</param> | |
/// <param name="commandParameters">Parameters of the command to execute.</param> | |
/// <returns>An object representing the result of the command, if applicable.</returns> | |
public object ExecuteCdpCommand(string commandName, Dictionary<string, object> commandParameters) | |
{ | |
if (commandName == null) | |
{ | |
throw new ArgumentNullException(nameof(commandName), "commandName must not be null"); | |
} | |
Dictionary<string, object> parameters = new Dictionary<string, object>(); | |
parameters["cmd"] = commandName; | |
parameters["params"] = commandParameters; | |
Response response = this.Execute(ExecuteCdp, parameters); | |
return response.Value; | |
} | |
/// <summary> | |
/// Executes a custom Chrome command. | |
/// </summary> | |
/// <param name="commandName">Name of the command to execute.</param> | |
/// <param name="commandParameters">Parameters of the command to execute.</param> | |
[Obsolete("ExecuteChromeCommand is deprecated in favor of ExecuteCdpCommand.")] | |
public void ExecuteChromeCommand(string commandName, Dictionary<string, object> commandParameters) | |
{ | |
ExecuteCdpCommand(commandName, commandParameters); | |
} | |
/// <summary> | |
/// Executes a custom Chrome command. | |
/// </summary> | |
/// <param name="commandName">Name of the command to execute.</param> | |
/// <param name="commandParameters">Parameters of the command to execute.</param> | |
/// <returns>An object representing the result of the command.</returns> | |
[Obsolete("ExecuteChromeCommandWithResult is deprecated in favor of ExecuteCdpCommand.")] | |
public object ExecuteChromeCommandWithResult(string commandName, Dictionary<string, object> commandParameters) | |
{ | |
return ExecuteCdpCommand(commandName, commandParameters); | |
} | |
/// <summary> | |
/// Creates a session to communicate with a browser using the Chromium Developer Tools debugging protocol. | |
/// </summary> | |
/// <param name="devToolsProtocolVersion">The version of the Chromium Developer Tools protocol to use. Defaults to autodetect the protocol version.</param> | |
/// <returns>The active session to use to communicate with the Chromium Developer Tools debugging protocol.</returns> | |
public DevToolsSession GetDevToolsSession() | |
{ | |
return GetDevToolsSession(DevToolsSession.AutoDetectDevToolsProtocolVersion); | |
} | |
/// <summary> | |
/// Creates a session to communicate with a browser using the Chromium Developer Tools debugging protocol. | |
/// </summary> | |
/// <param name="devToolsProtocolVersion">The version of the Chromium Developer Tools protocol to use. Defaults to autodetect the protocol version.</param> | |
/// <returns>The active session to use to communicate with the Chromium Developer Tools debugging protocol.</returns> | |
public DevToolsSession GetDevToolsSession(int devToolsProtocolVersion) | |
{ | |
if (this.devToolsSession == null) | |
{ | |
if (!this.Capabilities.HasCapability(this.optionsCapabilityName)) | |
{ | |
throw new WebDriverException("Cannot find " + this.optionsCapabilityName + " capability for driver"); | |
} | |
Dictionary<string, object> options = this.Capabilities.GetCapability(this.optionsCapabilityName) as Dictionary<string, object>; | |
if (options == null) | |
{ | |
throw new WebDriverException("Found " + this.optionsCapabilityName + " capability, but is not an object"); | |
} | |
if (!options.ContainsKey("debuggerAddress")) | |
{ | |
throw new WebDriverException("Did not find debuggerAddress capability in " + this.optionsCapabilityName); | |
} | |
string debuggerAddress = options["debuggerAddress"].ToString(); | |
try | |
{ | |
// DevToolsSession session = new DevToolsSession(debuggerAddress); | |
// session.StartSession(devToolsProtocolVersion).ConfigureAwait(false).GetAwaiter().GetResult(); | |
// this.devToolsSession = session; | |
} | |
catch (Exception e) | |
{ | |
throw new WebDriverException("Unexpected error creating WebSocket DevTools session.", e); | |
} | |
} | |
return this.devToolsSession; | |
} | |
/// <summary> | |
/// Closes a DevTools session. | |
/// </summary> | |
public void CloseDevToolsSession() | |
{ | |
if (this.devToolsSession != null) | |
{ | |
// this.devToolsSession.StopSession(true).ConfigureAwait(false).GetAwaiter().GetResult(); | |
} | |
} | |
/// <summary> | |
/// Clears simulated network conditions. | |
/// </summary> | |
public void ClearNetworkConditions() | |
{ | |
this.Execute(DeleteNetworkConditionsCommand, null); | |
} | |
/// <summary> | |
/// Returns the list of cast sinks (Cast devices) available to the Chrome media router. | |
/// </summary> | |
/// <returns>The list of available sinks.</returns> | |
public List<Dictionary<string, string>> GetCastSinks() | |
{ | |
List<Dictionary<string, string>> returnValue = new List<Dictionary<string, string>>(); | |
Response response = this.Execute(GetCastSinksCommand, null); | |
object[] responseValue = response.Value as object[]; | |
if (responseValue != null) | |
{ | |
foreach (object entry in responseValue) | |
{ | |
Dictionary<string, object> entryValue = entry as Dictionary<string, object>; | |
if (entryValue != null) | |
{ | |
Dictionary<string, string> sink = new Dictionary<string, string>(); | |
foreach (KeyValuePair<string, object> pair in entryValue) | |
{ | |
sink[pair.Key] = pair.Value.ToString(); | |
} | |
returnValue.Add(sink); | |
} | |
} | |
} | |
return returnValue; | |
} | |
/// <summary> | |
/// Selects a cast sink (Cast device) as the recipient of media router intents (connect or play). | |
/// </summary> | |
/// <param name="deviceName">Name of the target sink (device).</param> | |
public void SelectCastSink(string deviceName) | |
{ | |
if (deviceName == null) | |
{ | |
throw new ArgumentNullException(nameof(deviceName), "deviceName must not be null"); | |
} | |
Dictionary<string, object> parameters = new Dictionary<string, object>(); | |
parameters["sinkName"] = deviceName; | |
this.Execute(SelectCastSinkCommand, parameters); | |
} | |
/// <summary> | |
/// Initiates tab mirroring for the current browser tab on the specified device. | |
/// </summary> | |
/// <param name="deviceName">Name of the target sink (device).</param> | |
public void StartTabMirroring(string deviceName) | |
{ | |
if (deviceName == null) | |
{ | |
throw new ArgumentNullException(nameof(deviceName), "deviceName must not be null"); | |
} | |
Dictionary<string, object> parameters = new Dictionary<string, object>(); | |
parameters["sinkName"] = deviceName; | |
this.Execute(StartCastTabMirroringCommand, parameters); | |
} | |
/// <summary> | |
/// Initiates mirroring of the desktop on the specified device. | |
/// </summary> | |
/// <param name="deviceName">Name of the target sink (device).</param> | |
public void StartDesktopMirroring(string deviceName) | |
{ | |
if (deviceName == null) | |
{ | |
throw new ArgumentNullException(nameof(deviceName), "deviceName must not be null"); | |
} | |
Dictionary<string, object> parameters = new Dictionary<string, object>(); | |
parameters["sinkName"] = deviceName; | |
this.Execute(StartCastDesktopMirroringCommand, parameters); | |
} | |
/// <summary> | |
/// Returns the error message if there is any issue in a Cast session. | |
/// </summary> | |
/// <returns>An error message.</returns> | |
public String GetCastIssueMessage() | |
{ | |
Response response = this.Execute(GetCastIssueMessageCommand, null); | |
return (string)response.Value; | |
} | |
/// <summary> | |
/// Stops casting from media router to the specified device, if connected. | |
/// </summary> | |
/// <param name="deviceName">Name of the target sink (device).</param> | |
public void StopCasting(string deviceName) | |
{ | |
if (deviceName == null) | |
{ | |
throw new ArgumentNullException(nameof(deviceName), "deviceName must not be null"); | |
} | |
Dictionary<string, object> parameters = new Dictionary<string, object>(); | |
parameters["sinkName"] = deviceName; | |
this.Execute(StopCastingCommand, parameters); | |
} | |
protected override void Dispose(bool disposing) | |
{ | |
if (disposing) | |
{ | |
if (this.devToolsSession != null) | |
{ | |
this.devToolsSession.Dispose(); | |
this.devToolsSession = null; | |
} | |
} | |
base.Dispose(disposing); | |
} | |
private static ICapabilities ConvertOptionsToCapabilities(ChromiumOptions options) | |
{ | |
if (options == null) | |
{ | |
throw new ArgumentNullException(nameof(options), "options must not be null"); | |
} | |
return options.ToCapabilities(); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// <copyright file="DriverServiceCommandExecutor.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; | |
namespace OpenQA.Selenium.Remote | |
{ | |
/// <summary> | |
/// Provides a mechanism to execute commands on the browser | |
/// </summary> | |
public class MyDriverServiceCommandExecutor : ICommandExecutor | |
{ | |
private DriverService service; | |
private MyHttpCommandExecutor internalExecutor; | |
private bool isDisposed; | |
/// <summary> | |
/// Initializes a new instance of the <see cref="DriverServiceCommandExecutor"/> class. | |
/// </summary> | |
/// <param name="driverService">The <see cref="DriverService"/> that drives the browser.</param> | |
/// <param name="commandTimeout">The maximum amount of time to wait for each command.</param> | |
public MyDriverServiceCommandExecutor(DriverService driverService, TimeSpan commandTimeout) | |
: this(driverService, commandTimeout, true) | |
{ | |
} | |
/// <summary> | |
/// Initializes a new instance of the <see cref="DriverServiceCommandExecutor"/> class. | |
/// </summary> | |
/// <param name="driverService">The <see cref="DriverService"/> that drives the browser.</param> | |
/// <param name="commandTimeout">The maximum amount of time to wait for each command.</param> | |
/// <param name="enableKeepAlive"><see langword="true"/> if the KeepAlive header should be sent | |
/// with HTTP requests; otherwise, <see langword="false"/>.</param> | |
public MyDriverServiceCommandExecutor(DriverService driverService, TimeSpan commandTimeout, bool enableKeepAlive) | |
{ | |
this.service = driverService; | |
this.internalExecutor = new MyHttpCommandExecutor(driverService.ServiceUrl, commandTimeout, enableKeepAlive); | |
} | |
/// <summary> | |
/// Initializes a new instance of the <see cref="DriverServiceCommandExecutor"/> class. | |
/// </summary> | |
/// <param name="service">The <see cref="DriverService"/> that drives the browser.</param> | |
/// <param name="commandExecutor">The <see cref="HttpCommandExecutor"/> object used to execute commands, | |
/// communicating with the service via HTTP.</param> | |
public MyDriverServiceCommandExecutor(DriverService service, MyHttpCommandExecutor commandExecutor) | |
{ | |
this.service = service; | |
this.internalExecutor = commandExecutor; | |
} | |
/// <summary> | |
/// Gets the <see cref="CommandInfoRepository"/> object associated with this executor. | |
/// </summary> | |
//public CommandInfoRepository CommandInfoRepository | |
//{ | |
// get { return this.internalExecutor.CommandInfoRepository; } | |
//} | |
public bool TryAddCommand(string commandName, CommandInfo info) | |
{ | |
return this.internalExecutor.TryAddCommand(commandName, info); | |
} | |
/// <summary> | |
/// Gets the <see cref="HttpCommandExecutor"/> that sends commands to the remote | |
/// end WebDriver implementation. | |
/// </summary> | |
public MyHttpCommandExecutor HttpExecutor | |
{ | |
get { return this.internalExecutor; } | |
} | |
/// <summary> | |
/// Executes a command | |
/// </summary> | |
/// <param name="commandToExecute">The command you wish to execute</param> | |
/// <returns>A response from the browser</returns> | |
public Response Execute(Command commandToExecute) | |
{ | |
if (commandToExecute == null) | |
{ | |
throw new ArgumentNullException(nameof(commandToExecute), "Command to execute cannot be null"); | |
} | |
Response toReturn = null; | |
if (commandToExecute.Name == DriverCommand.NewSession) | |
{ | |
this.service.Start(); | |
} | |
// Use a try-catch block to catch exceptions for the Quit | |
// command, so that we can get the finally block. | |
try | |
{ | |
toReturn = this.internalExecutor.Execute(commandToExecute); | |
} | |
finally | |
{ | |
if (commandToExecute.Name == DriverCommand.Quit) | |
{ | |
this.Dispose(); | |
} | |
} | |
return toReturn; | |
} | |
/// <summary> | |
/// Releases all resources used by the <see cref="DriverServiceCommandExecutor"/>. | |
/// </summary> | |
public void Dispose() | |
{ | |
this.Dispose(true); | |
} | |
/// <summary> | |
/// Releases the unmanaged resources used by the <see cref="HttpCommandExecutor"/> and | |
/// optionally releases the managed resources. | |
/// </summary> | |
/// <param name="disposing"><see langword="true"/> to release managed and resources; | |
/// <see langword="false"/> to only release unmanaged resources.</param> | |
protected virtual void Dispose(bool disposing) | |
{ | |
if (!this.isDisposed) | |
{ | |
if (disposing) | |
{ | |
this.internalExecutor.Dispose(); | |
this.service.Dispose(); | |
} | |
this.isDisposed = true; | |
} | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// <copyright file="MyHttpCommandExecutor.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.Generic; | |
using System.Diagnostics; | |
using System.Globalization; | |
using System.IO; | |
using System.Net; | |
using System.Net.Http; | |
using System.Net.Http.Headers; | |
using System.Text; | |
using System.Threading; | |
using System.Threading.Tasks; | |
using OpenQA.Selenium.Internal; | |
namespace OpenQA.Selenium.Remote | |
{ | |
/// <summary> | |
/// Provides a way of executing Commands over HTTP | |
/// </summary> | |
public class MyHttpCommandExecutor : ICommandExecutor | |
{ | |
private const string JsonMimeType = "application/json"; | |
private const string PngMimeType = "image/png"; | |
private const string Utf8CharsetType = "utf-8"; | |
private const string RequestAcceptHeader = JsonMimeType + ", " + PngMimeType; | |
private const string RequestContentTypeHeader = JsonMimeType + "; charset=" + Utf8CharsetType; | |
private const string UserAgentHeaderTemplate = "selenium/{0} (.net {1})"; | |
private Uri remoteServerUri; | |
private TimeSpan serverResponseTimeout; | |
private string userAgent; | |
private bool enableKeepAlive; | |
private bool isDisposed; | |
private IWebProxy proxy; | |
private CommandInfoRepository commandInfoRepository = new W3CWireProtocolCommandInfoRepository(); | |
private HttpClient client; | |
/// <summary> | |
/// Initializes a new instance of the <see cref="MyHttpCommandExecutor"/> class | |
/// </summary> | |
/// <param name="addressOfRemoteServer">Address of the WebDriver Server</param> | |
/// <param name="timeout">The timeout within which the server must respond.</param> | |
public MyHttpCommandExecutor(Uri addressOfRemoteServer, TimeSpan timeout) | |
: this(addressOfRemoteServer, timeout, true) | |
{ | |
} | |
/// <summary> | |
/// Initializes a new instance of the <see cref="MyHttpCommandExecutor"/> class | |
/// </summary> | |
/// <param name="addressOfRemoteServer">Address of the WebDriver Server</param> | |
/// <param name="timeout">The timeout within which the server must respond.</param> | |
/// <param name="enableKeepAlive"><see langword="true"/> if the KeepAlive header should be sent | |
/// with HTTP requests; otherwise, <see langword="false"/>.</param> | |
public MyHttpCommandExecutor(Uri addressOfRemoteServer, TimeSpan timeout, bool enableKeepAlive) | |
{ | |
if (addressOfRemoteServer == null) | |
{ | |
throw new ArgumentNullException(nameof(addressOfRemoteServer), "You must specify a remote address to connect to"); | |
} | |
if (!addressOfRemoteServer.AbsoluteUri.EndsWith("/", StringComparison.OrdinalIgnoreCase)) | |
{ | |
addressOfRemoteServer = new Uri(addressOfRemoteServer.ToString() + "/"); | |
} | |
this.userAgent = string.Format(CultureInfo.InvariantCulture, UserAgentHeaderTemplate, ResourceUtilities.AssemblyVersion, ResourceUtilities.PlatformFamily); | |
this.remoteServerUri = addressOfRemoteServer; | |
this.serverResponseTimeout = timeout; | |
this.enableKeepAlive = enableKeepAlive; | |
} | |
/// <summary> | |
/// Occurs when the <see cref="MyHttpCommandExecutor"/> is sending an HTTP | |
/// request to the remote end WebDriver implementation. | |
/// </summary> | |
public event EventHandler<SendingRemoteHttpRequestEventArgs> SendingRemoteHttpRequest; | |
/// <summary> | |
/// Gets or sets an <see cref="IWebProxy"/> object to be used to proxy requests | |
/// between this <see cref="MyHttpCommandExecutor"/> and the remote end WebDriver | |
/// implementation. | |
/// </summary> | |
public IWebProxy Proxy | |
{ | |
get { return this.proxy; } | |
set { this.proxy = value; } | |
} | |
/// <summary> | |
/// Gets or sets a value indicating whether keep-alive is enabled for HTTP | |
/// communication between this <see cref="MyHttpCommandExecutor"/> and the | |
/// remote end WebDriver implementation. | |
/// </summary> | |
public bool IsKeepAliveEnabled | |
{ | |
get { return this.enableKeepAlive; } | |
set { this.enableKeepAlive = value; } | |
} | |
/// <summary> | |
/// Gets or sets the user agent string used for HTTP communication | |
/// batween this <see cref="MyHttpCommandExecutor"/> and the remote end | |
/// WebDriver implementation | |
/// </summary> | |
public string UserAgent | |
{ | |
get { return this.userAgent; } | |
set { this.userAgent = value; } | |
} | |
/// <summary> | |
/// Gets the repository of objects containing information about commands. | |
/// </summary> | |
protected CommandInfoRepository CommandInfoRepository | |
{ | |
get { return this.commandInfoRepository; } | |
set { this.commandInfoRepository = value; } | |
} | |
/// <summary> | |
/// Attempts to add a command to the repository of commands known to this executor. | |
/// </summary> | |
/// <param name="commandName">The name of the command to attempt to add.</param> | |
/// <param name="info">The <see cref="CommandInfo"/> describing the commnd to add.</param> | |
/// <returns><see langword="true"/> if the new command has been added successfully; otherwise, <see langword="false"/>.</returns> | |
public bool TryAddCommand(string commandName, CommandInfo info) | |
{ | |
HttpCommandInfo commandInfo = info as HttpCommandInfo; | |
if (commandInfo == null) | |
{ | |
return false; | |
} | |
return this.commandInfoRepository.TryAddCommand(commandName, commandInfo); | |
} | |
/// <summary> | |
/// Executes a command | |
/// </summary> | |
/// <param name="commandToExecute">The command you wish to execute</param> | |
/// <returns>A response from the browser</returns> | |
public virtual Response Execute(Command commandToExecute) | |
{ | |
if (commandToExecute == null) | |
{ | |
throw new ArgumentNullException(nameof(commandToExecute), "commandToExecute cannot be null"); | |
} | |
HttpCommandInfo info = this.commandInfoRepository.GetCommandInfo<HttpCommandInfo>(commandToExecute.Name); | |
if (info == null) | |
{ | |
throw new NotImplementedException(string.Format("The command you are attempting to execute, {0}, does not exist in the protocol dialect used by the remote end.", commandToExecute.Name)); | |
} | |
if (this.client == null) | |
{ | |
this.CreateHttpClient(); | |
} | |
HttpRequestInfo requestInfo = new HttpRequestInfo(this.remoteServerUri, commandToExecute, info); | |
HttpResponseInfo responseInfo = null; | |
try | |
{ | |
// Use TaskFactory to avoid deadlock in multithreaded implementations. | |
responseInfo = new TaskFactory(CancellationToken.None, | |
TaskCreationOptions.None, | |
TaskContinuationOptions.None, | |
TaskScheduler.Default) | |
.StartNew(() => this.MakeHttpRequest(requestInfo)) | |
.Unwrap() | |
.GetAwaiter() | |
.GetResult(); | |
} | |
catch (HttpRequestException ex) | |
{ | |
WebException innerWebException = ex.InnerException as WebException; | |
if (innerWebException != null) | |
{ | |
if (innerWebException.Status == WebExceptionStatus.Timeout) | |
{ | |
string timeoutMessage = "The HTTP request to the remote WebDriver server for URL {0} timed out after {1} seconds."; | |
throw new WebDriverException(string.Format(CultureInfo.InvariantCulture, timeoutMessage, requestInfo.FullUri.AbsoluteUri, this.serverResponseTimeout.TotalSeconds), ex); | |
} | |
else if (innerWebException.Status == WebExceptionStatus.ConnectFailure) | |
{ | |
string connectFailureMessage = "Could not connect to the remote WebDriver server for URL {0}."; | |
throw new WebDriverException(string.Format(CultureInfo.InvariantCulture, connectFailureMessage, requestInfo.FullUri.AbsoluteUri, this.serverResponseTimeout.TotalSeconds), ex); | |
} | |
else if (innerWebException.Response == null) | |
{ | |
string nullResponseMessage = "A exception with a null response was thrown sending an HTTP request to the remote WebDriver server for URL {0}. The status of the exception was {1}, and the message was: {2}"; | |
throw new WebDriverException(string.Format(CultureInfo.InvariantCulture, nullResponseMessage, requestInfo.FullUri.AbsoluteUri, innerWebException.Status, innerWebException.Message), innerWebException); | |
} | |
} | |
string unknownErrorMessage = "An unknown exception was encountered sending an HTTP request to the remote WebDriver server for URL {0}. The exception message was: {1}"; | |
throw new WebDriverException(string.Format(CultureInfo.InvariantCulture, unknownErrorMessage, requestInfo.FullUri.AbsoluteUri, ex.Message), ex); | |
} | |
catch (TaskCanceledException ex) | |
{ | |
string timeoutMessage = "The HTTP request to the remote WebDriver server for URL {0} timed out after {1} seconds."; | |
throw new WebDriverException(string.Format(CultureInfo.InvariantCulture, timeoutMessage, requestInfo.FullUri.AbsoluteUri, this.serverResponseTimeout.TotalSeconds), ex); | |
} | |
Response toReturn = this.CreateResponse(responseInfo); | |
return toReturn; | |
} | |
/// <summary> | |
/// Raises the <see cref="SendingRemoteHttpRequest"/> event. | |
/// </summary> | |
/// <param name="eventArgs">A <see cref="SendingRemoteHttpRequestEventArgs"/> that contains the event data.</param> | |
protected virtual void OnSendingRemoteHttpRequest(SendingRemoteHttpRequestEventArgs eventArgs) | |
{ | |
if (eventArgs == null) | |
{ | |
throw new ArgumentNullException(nameof(eventArgs), "eventArgs must not be null"); | |
} | |
if (this.SendingRemoteHttpRequest != null) | |
{ | |
this.SendingRemoteHttpRequest(this, eventArgs); | |
} | |
} | |
private void CreateHttpClient() | |
{ | |
HttpClientHandler httpClientHandler = new HttpClientHandler(); | |
string userInfo = this.remoteServerUri.UserInfo; | |
if (!string.IsNullOrEmpty(userInfo) && userInfo.Contains(":")) | |
{ | |
string[] userInfoComponents = this.remoteServerUri.UserInfo.Split(new char[] { ':' }, 2); | |
httpClientHandler.Credentials = new NetworkCredential(userInfoComponents[0], userInfoComponents[1]); | |
httpClientHandler.PreAuthenticate = true; | |
} | |
// httpClientHandler.Proxy = this.Proxy; | |
this.client = new HttpClient(httpClientHandler); | |
this.client.DefaultRequestHeaders.UserAgent.ParseAdd(this.UserAgent); | |
this.client.DefaultRequestHeaders.Accept.ParseAdd(RequestAcceptHeader); | |
this.client.DefaultRequestHeaders.ExpectContinue = false; | |
if (!this.IsKeepAliveEnabled) | |
{ | |
this.client.DefaultRequestHeaders.Connection.ParseAdd("close"); | |
} | |
this.client.Timeout = this.serverResponseTimeout; | |
} | |
private async Task<HttpResponseInfo> MakeHttpRequest(HttpRequestInfo requestInfo) | |
{ | |
SendingRemoteHttpRequestEventArgs eventArgs = new SendingRemoteHttpRequestEventArgs(requestInfo.HttpMethod, requestInfo.FullUri.ToString(), requestInfo.RequestBody); | |
this.OnSendingRemoteHttpRequest(eventArgs); | |
HttpMethod method = new HttpMethod(requestInfo.HttpMethod); | |
using (HttpRequestMessage requestMessage = new HttpRequestMessage(method, requestInfo.FullUri)) | |
{ | |
foreach (KeyValuePair<string, string> header in eventArgs.Headers) | |
{ | |
requestMessage.Headers.Add(header.Key, header.Value); | |
} | |
if (requestInfo.HttpMethod == HttpCommandInfo.GetCommand) | |
{ | |
CacheControlHeaderValue cacheControlHeader = new CacheControlHeaderValue(); | |
cacheControlHeader.NoCache = true; | |
requestMessage.Headers.CacheControl = cacheControlHeader; | |
} | |
if (requestInfo.HttpMethod == HttpCommandInfo.PostCommand) | |
{ | |
MediaTypeWithQualityHeaderValue acceptHeader = new MediaTypeWithQualityHeaderValue(JsonMimeType); | |
acceptHeader.CharSet = Utf8CharsetType; | |
requestMessage.Headers.Accept.Add(acceptHeader); | |
byte[] bytes = Encoding.UTF8.GetBytes(eventArgs.RequestBody); | |
requestMessage.Content = new ByteArrayContent(bytes, 0, bytes.Length); | |
MediaTypeHeaderValue contentTypeHeader = new MediaTypeHeaderValue(JsonMimeType); | |
contentTypeHeader.CharSet = Utf8CharsetType; | |
requestMessage.Content.Headers.ContentType = contentTypeHeader; | |
} | |
using (HttpResponseMessage responseMessage = await this.client.SendAsync(requestMessage)) | |
{ | |
HttpResponseInfo httpResponseInfo = new HttpResponseInfo(); | |
httpResponseInfo.Body = await responseMessage.Content.ReadAsStringAsync(); | |
httpResponseInfo.ContentType = responseMessage.Content.Headers.ContentType.ToString(); | |
httpResponseInfo.StatusCode = responseMessage.StatusCode; | |
return httpResponseInfo; | |
} | |
} | |
} | |
private Response CreateResponse(HttpResponseInfo responseInfo) | |
{ | |
Response response = new Response(); | |
string body = responseInfo.Body; | |
if (responseInfo.ContentType != null && responseInfo.ContentType.StartsWith(JsonMimeType, StringComparison.OrdinalIgnoreCase)) | |
{ | |
response = Response.FromJson(body); | |
} | |
else | |
{ | |
response.Value = body; | |
} | |
if (response.Value is string) | |
{ | |
response.Value = ((string)response.Value).Replace("\r\n", "\n").Replace("\n", Environment.NewLine); | |
} | |
return response; | |
} | |
/// <summary> | |
/// Releases all resources used by the <see cref="MyHttpCommandExecutor"/>. | |
/// </summary> | |
public void Dispose() | |
{ | |
this.Dispose(true); | |
} | |
/// <summary> | |
/// Releases the unmanaged resources used by the <see cref="MyHttpCommandExecutor"/> and | |
/// optionally releases the managed resources. | |
/// </summary> | |
/// <param name="disposing"><see langword="true"/> to release managed and resources; | |
/// <see langword="false"/> to only release unmanaged resources.</param> | |
protected virtual void Dispose(bool disposing) | |
{ | |
if (!this.isDisposed) | |
{ | |
if (this.client != null) | |
{ | |
this.client.Dispose(); | |
} | |
this.isDisposed = true; | |
} | |
} | |
private class HttpRequestInfo | |
{ | |
public HttpRequestInfo(Uri serverUri, Command commandToExecute, HttpCommandInfo commandInfo) | |
{ | |
this.FullUri = commandInfo.CreateCommandUri(serverUri, commandToExecute); | |
this.HttpMethod = commandInfo.Method; | |
this.RequestBody = commandToExecute.ParametersAsJsonString; | |
} | |
public Uri FullUri { get; set; } | |
public string HttpMethod { get; set; } | |
public string RequestBody { get; set; } | |
} | |
private class HttpResponseInfo | |
{ | |
public HttpStatusCode StatusCode { get; set; } | |
public string Body { get; set; } | |
public string ContentType { get; set; } | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment