Skip to content

Commit

Permalink
Adding Chrome DevTools Protocol (CDP) support to .NET bindings
Browse files Browse the repository at this point in the history
  • Loading branch information
jimevans committed Jul 8, 2019
1 parent fafa447 commit 103245a
Show file tree
Hide file tree
Showing 499 changed files with 26,869 additions and 6 deletions.
7 changes: 7 additions & 0 deletions dotnet/src/webdriver/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ load(
"*.cs",
"Chrome/*.cs",
"Chromium/*.cs",
"DevTools/**/*.cs",
"Edge/*.cs",
"Firefox/**/*.cs",
"Html5/*.cs",
Expand All @@ -40,6 +41,7 @@ load(
"@io_bazel_rules_dotnet//dotnet/stdlib.net:system.io.compression.dll",
"@io_bazel_rules_dotnet//dotnet/stdlib.net:system.io.compression.filesystem.dll",
"@io_bazel_rules_dotnet//dotnet/stdlib.net:system.net.http.dll",
"@io_bazel_rules_dotnet//dotnet/stdlib.net:system.runtime.serialization.dll",
"@io_bazel_rules_dotnet//dotnet/stdlib.net:system.xml.dll",
"@json.net//:net45",
],
Expand All @@ -51,6 +53,7 @@ core_library(
"*.cs",
"Chrome/*.cs",
"Chromium/*.cs",
"DevTools/**/*.cs",
"Edge/*.cs",
"Firefox/**/*.cs",
"Html5/*.cs",
Expand Down Expand Up @@ -83,6 +86,7 @@ core_library(
"*.cs",
"Chrome/*.cs",
"Chromium/*.cs",
"DevTools/**/*.cs",
"Edge/*.cs",
"Firefox/**/*.cs",
"Html5/*.cs",
Expand Down Expand Up @@ -110,6 +114,7 @@ core_library(
"@io_bazel_rules_dotnet//dotnet/stdlib.net:system.io.compression.dll",
"@io_bazel_rules_dotnet//dotnet/stdlib.net:system.io.compression.filesystem.dll",
"@io_bazel_rules_dotnet//dotnet/stdlib.net:system.net.http.dll",
"@io_bazel_rules_dotnet//dotnet/stdlib.net:system.runtime.serialization.dll",
"@io_bazel_rules_dotnet//dotnet/stdlib.net:system.xml.dll",
"@json.net//:net45",
],
Expand All @@ -120,6 +125,8 @@ core_library(
srcs = glob([
"*.cs",
"Chrome/*.cs",
"Chromium/*.cs",
"DevTools/**/*.cs",
"Edge/*.cs",
"Firefox/**/*.cs",
"Html5/*.cs",
Expand Down
68 changes: 66 additions & 2 deletions dotnet/src/webdriver/Chromium/ChromiumDriver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,18 @@
// </copyright>

using System;
using OpenQA.Selenium.Remote;
using System.Collections.Generic;
using System.Globalization;
using System.Net.Http;
using System.Threading.Tasks;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using OpenQA.Selenium.DevTools;
using OpenQA.Selenium.Remote;

namespace OpenQA.Selenium.Chromium
{
public abstract class ChromiumDriver : RemoteWebDriver, ISupportsLogs
public abstract class ChromiumDriver : RemoteWebDriver, ISupportsLogs, IDevTools
{
/// <summary>
/// Accept untrusted SSL Certificates
Expand Down Expand Up @@ -135,6 +141,64 @@ public object ExecuteChromeCommandWithResult(string commandName, Dictionary<stri
return response.Value;
}

/// <summary>
/// Creates a session to communicate with a browser using the Chromium Developer Tools debugging protocol.
/// </summary>
/// <returns>The active session to use to communicate with the Chromium Developer Tools debugging protocol.</returns>
public DevToolsSession CreateDevToolsSession()
{
if (!this.Capabilities.HasCapability(ChromiumOptions.DefaultCapability))
{
throw new WebDriverException("Cannot find " + ChromiumOptions.DefaultCapability + " capability for driver");
}

Dictionary<string, object> options = this.Capabilities.GetCapability(ChromiumOptions.DefaultCapability) as Dictionary<string, object>;
if (options == null)
{
throw new WebDriverException("Found " + ChromiumOptions.DefaultCapability + " capability, but is not an object");
}

if (!options.ContainsKey("debuggerAddress"))
{
throw new WebDriverException("Did not find debuggerAddress capability in goog:chromeOptions");
}

string debuggerAddress = options["debuggerAddress"].ToString();
try
{
string debuggerUrl = string.Format(CultureInfo.InvariantCulture, "http://{0}/", debuggerAddress);
string rawDebuggerInfo = string.Empty;
using (HttpClient client = new HttpClient())
{
client.BaseAddress = new Uri(debuggerUrl);
rawDebuggerInfo = client.GetStringAsync("/json").ConfigureAwait(false).GetAwaiter().GetResult();
}

string webSocketUrl = null;
string targetId = null;
var sessions = JsonConvert.DeserializeObject<ICollection<DevToolsSessionInfo>>(rawDebuggerInfo);
foreach (var target in sessions)
{
if (target.Type == "page")
{
targetId = target.Id;
webSocketUrl = target.WebSocketDebuggerUrl;
break;
}
}

DevToolsSession session = new DevToolsSession(webSocketUrl);
var foo = session.Target.AttachToTarget(new DevTools.Target.AttachToTargetCommandSettings() { TargetId = targetId }).ConfigureAwait(false).GetAwaiter().GetResult();
var t1 = session.Target.SetAutoAttach(new DevTools.Target.SetAutoAttachCommandSettings() { AutoAttach = true, WaitForDebuggerOnStart = false }).ConfigureAwait(false).GetAwaiter().GetResult();
var t2 = session.Log.Clear().ConfigureAwait(false).GetAwaiter().GetResult();
return session;
}
catch (Exception e)
{
throw new WebDriverException("Unexpected error creating WebSocket DevTools session.", e);
}
}

private static ICapabilities ConvertOptionsToCapabilities(ChromiumOptions options)
{
if (options == null)
Expand Down
56 changes: 56 additions & 0 deletions dotnet/src/webdriver/DevTools/AutoGenerated/Browser/Bounds.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
namespace OpenQA.Selenium.DevTools.Browser
{
using Newtonsoft.Json;

/// <summary>
/// Browser window bounds information
/// </summary>
public sealed class Bounds
{
/// <summary>
/// The offset from the left edge of the screen to the window in pixels.
///</summary>
[JsonProperty("left", DefaultValueHandling = DefaultValueHandling.Ignore)]
public long? Left
{
get;
set;
}
/// <summary>
/// The offset from the top edge of the screen to the window in pixels.
///</summary>
[JsonProperty("top", DefaultValueHandling = DefaultValueHandling.Ignore)]
public long? Top
{
get;
set;
}
/// <summary>
/// The window width in pixels.
///</summary>
[JsonProperty("width", DefaultValueHandling = DefaultValueHandling.Ignore)]
public long? Width
{
get;
set;
}
/// <summary>
/// The window height in pixels.
///</summary>
[JsonProperty("height", DefaultValueHandling = DefaultValueHandling.Ignore)]
public long? Height
{
get;
set;
}
/// <summary>
/// The window state. Default to normal.
///</summary>
[JsonProperty("windowState", DefaultValueHandling = DefaultValueHandling.Ignore)]
public WindowState? WindowState
{
get;
set;
}
}
}
139 changes: 139 additions & 0 deletions dotnet/src/webdriver/DevTools/AutoGenerated/Browser/BrowserAdapter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
namespace OpenQA.Selenium.DevTools.Browser
{
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;

/// <summary>
/// Represents an adapter for the Browser domain to simplify the command interface.
/// </summary>
public class BrowserAdapter
{
private readonly DevToolsSession m_session;
private readonly string m_domainName = "Browser";
private Dictionary<string, DevToolsEventData> m_eventMap = new Dictionary<string, DevToolsEventData>();

public BrowserAdapter(DevToolsSession session)
{
m_session = session ?? throw new ArgumentNullException(nameof(session));
m_session.DevToolsEventReceived += OnDevToolsEventReceived;
}

/// <summary>
/// Gets the DevToolsSession associated with the adapter.
/// </summary>
public DevToolsSession Session
{
get { return m_session; }
}


/// <summary>
/// Grant specific permissions to the given origin and reject all others.
/// </summary>
public async Task<GrantPermissionsCommandResponse> GrantPermissions(GrantPermissionsCommandSettings command, CancellationToken cancellationToken = default(CancellationToken), int? millisecondsTimeout = null, bool throwExceptionIfResponseNotReceived = true)
{
return await m_session.SendCommand<GrantPermissionsCommandSettings, GrantPermissionsCommandResponse>(command, cancellationToken, millisecondsTimeout, throwExceptionIfResponseNotReceived);
}
/// <summary>
/// Reset all permission management for all origins.
/// </summary>
public async Task<ResetPermissionsCommandResponse> ResetPermissions(ResetPermissionsCommandSettings command, CancellationToken cancellationToken = default(CancellationToken), int? millisecondsTimeout = null, bool throwExceptionIfResponseNotReceived = true)
{
return await m_session.SendCommand<ResetPermissionsCommandSettings, ResetPermissionsCommandResponse>(command, cancellationToken, millisecondsTimeout, throwExceptionIfResponseNotReceived);
}
/// <summary>
/// Close browser gracefully.
/// </summary>
public async Task<CloseCommandResponse> Close(CloseCommandSettings command = null, CancellationToken cancellationToken = default(CancellationToken), int? millisecondsTimeout = null, bool throwExceptionIfResponseNotReceived = true)
{
return await m_session.SendCommand<CloseCommandSettings, CloseCommandResponse>(command ?? new CloseCommandSettings(), cancellationToken, millisecondsTimeout, throwExceptionIfResponseNotReceived);
}
/// <summary>
/// Crashes browser on the main thread.
/// </summary>
public async Task<CrashCommandResponse> Crash(CrashCommandSettings command = null, CancellationToken cancellationToken = default(CancellationToken), int? millisecondsTimeout = null, bool throwExceptionIfResponseNotReceived = true)
{
return await m_session.SendCommand<CrashCommandSettings, CrashCommandResponse>(command ?? new CrashCommandSettings(), cancellationToken, millisecondsTimeout, throwExceptionIfResponseNotReceived);
}
/// <summary>
/// Crashes GPU process.
/// </summary>
public async Task<CrashGpuProcessCommandResponse> CrashGpuProcess(CrashGpuProcessCommandSettings command = null, CancellationToken cancellationToken = default(CancellationToken), int? millisecondsTimeout = null, bool throwExceptionIfResponseNotReceived = true)
{
return await m_session.SendCommand<CrashGpuProcessCommandSettings, CrashGpuProcessCommandResponse>(command ?? new CrashGpuProcessCommandSettings(), cancellationToken, millisecondsTimeout, throwExceptionIfResponseNotReceived);
}
/// <summary>
/// Returns version information.
/// </summary>
public async Task<GetVersionCommandResponse> GetVersion(GetVersionCommandSettings command = null, CancellationToken cancellationToken = default(CancellationToken), int? millisecondsTimeout = null, bool throwExceptionIfResponseNotReceived = true)
{
return await m_session.SendCommand<GetVersionCommandSettings, GetVersionCommandResponse>(command ?? new GetVersionCommandSettings(), cancellationToken, millisecondsTimeout, throwExceptionIfResponseNotReceived);
}
/// <summary>
/// Returns the command line switches for the browser process if, and only if
/// --enable-automation is on the commandline.
/// </summary>
public async Task<GetBrowserCommandLineCommandResponse> GetBrowserCommandLine(GetBrowserCommandLineCommandSettings command = null, CancellationToken cancellationToken = default(CancellationToken), int? millisecondsTimeout = null, bool throwExceptionIfResponseNotReceived = true)
{
return await m_session.SendCommand<GetBrowserCommandLineCommandSettings, GetBrowserCommandLineCommandResponse>(command ?? new GetBrowserCommandLineCommandSettings(), cancellationToken, millisecondsTimeout, throwExceptionIfResponseNotReceived);
}
/// <summary>
/// Get Chrome histograms.
/// </summary>
public async Task<GetHistogramsCommandResponse> GetHistograms(GetHistogramsCommandSettings command, CancellationToken cancellationToken = default(CancellationToken), int? millisecondsTimeout = null, bool throwExceptionIfResponseNotReceived = true)
{
return await m_session.SendCommand<GetHistogramsCommandSettings, GetHistogramsCommandResponse>(command, cancellationToken, millisecondsTimeout, throwExceptionIfResponseNotReceived);
}
/// <summary>
/// Get a Chrome histogram by name.
/// </summary>
public async Task<GetHistogramCommandResponse> GetHistogram(GetHistogramCommandSettings command, CancellationToken cancellationToken = default(CancellationToken), int? millisecondsTimeout = null, bool throwExceptionIfResponseNotReceived = true)
{
return await m_session.SendCommand<GetHistogramCommandSettings, GetHistogramCommandResponse>(command, cancellationToken, millisecondsTimeout, throwExceptionIfResponseNotReceived);
}
/// <summary>
/// Get position and size of the browser window.
/// </summary>
public async Task<GetWindowBoundsCommandResponse> GetWindowBounds(GetWindowBoundsCommandSettings command, CancellationToken cancellationToken = default(CancellationToken), int? millisecondsTimeout = null, bool throwExceptionIfResponseNotReceived = true)
{
return await m_session.SendCommand<GetWindowBoundsCommandSettings, GetWindowBoundsCommandResponse>(command, cancellationToken, millisecondsTimeout, throwExceptionIfResponseNotReceived);
}
/// <summary>
/// Get the browser window that contains the devtools target.
/// </summary>
public async Task<GetWindowForTargetCommandResponse> GetWindowForTarget(GetWindowForTargetCommandSettings command, CancellationToken cancellationToken = default(CancellationToken), int? millisecondsTimeout = null, bool throwExceptionIfResponseNotReceived = true)
{
return await m_session.SendCommand<GetWindowForTargetCommandSettings, GetWindowForTargetCommandResponse>(command, cancellationToken, millisecondsTimeout, throwExceptionIfResponseNotReceived);
}
/// <summary>
/// Set position and/or size of the browser window.
/// </summary>
public async Task<SetWindowBoundsCommandResponse> SetWindowBounds(SetWindowBoundsCommandSettings command, CancellationToken cancellationToken = default(CancellationToken), int? millisecondsTimeout = null, bool throwExceptionIfResponseNotReceived = true)
{
return await m_session.SendCommand<SetWindowBoundsCommandSettings, SetWindowBoundsCommandResponse>(command, cancellationToken, millisecondsTimeout, throwExceptionIfResponseNotReceived);
}
/// <summary>
/// Set dock tile details, platform-specific.
/// </summary>
public async Task<SetDockTileCommandResponse> SetDockTile(SetDockTileCommandSettings command, CancellationToken cancellationToken = default(CancellationToken), int? millisecondsTimeout = null, bool throwExceptionIfResponseNotReceived = true)
{
return await m_session.SendCommand<SetDockTileCommandSettings, SetDockTileCommandResponse>(command, cancellationToken, millisecondsTimeout, throwExceptionIfResponseNotReceived);
}

private void OnDevToolsEventReceived(object sender, DevToolsEventReceivedEventArgs e)
{
if (e.DomainName == m_domainName)
{
if (m_eventMap.ContainsKey(e.EventName))
{
var eventData = m_eventMap[e.EventName];
var eventArgs = e.EventData.ToObject(eventData.EventArgsType);
eventData.EventInvoker(eventArgs);
}
}
}

}
}
38 changes: 38 additions & 0 deletions dotnet/src/webdriver/DevTools/AutoGenerated/Browser/Bucket.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
namespace OpenQA.Selenium.DevTools.Browser
{
using Newtonsoft.Json;

/// <summary>
/// Chrome histogram bucket.
/// </summary>
public sealed class Bucket
{
/// <summary>
/// Minimum value (inclusive).
///</summary>
[JsonProperty("low")]
public long Low
{
get;
set;
}
/// <summary>
/// Maximum value (exclusive).
///</summary>
[JsonProperty("high")]
public long High
{
get;
set;
}
/// <summary>
/// Number of samples.
///</summary>
[JsonProperty("count")]
public long Count
{
get;
set;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
namespace OpenQA.Selenium.DevTools.Browser
{
using Newtonsoft.Json;

/// <summary>
/// Close browser gracefully.
/// </summary>
public sealed class CloseCommandSettings : ICommand
{
private const string DevToolsRemoteInterface_CommandName = "Browser.close";

[JsonIgnore]
public string CommandName
{
get { return DevToolsRemoteInterface_CommandName; }
}

}

public sealed class CloseCommandResponse : ICommandResponse<CloseCommandSettings>
{
}
}
Loading

0 comments on commit 103245a

Please sign in to comment.