Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[dotnet] Added function to enable halting targets until runtime.runIfWaitingForDebugger is invoked #13330

Merged
merged 14 commits into from
Jan 9, 2024
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 19 additions & 9 deletions dotnet/src/webdriver/Chromium/ChromiumDriver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -273,15 +273,14 @@ public object ExecuteCdpCommand(string commandName, Dictionary<string, object> c
/// <returns>The active session to use to communicate with the Chromium Developer Tools debugging protocol.</returns>
public DevToolsSession GetDevToolsSession()
{
return GetDevToolsSession(DevToolsSession.AutoDetectDevToolsProtocolVersion);
return GetDevToolsSession(new DevToolsOptions() { ProtocolVersion = 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)
public DevToolsSession GetDevToolsSession(DevToolsOptions options)
{
if (this.devToolsSession == null)
{
Expand All @@ -290,22 +289,22 @@ public DevToolsSession GetDevToolsSession(int devToolsProtocolVersion)
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)
Dictionary<string, object> optionsCapability = this.Capabilities.GetCapability(this.optionsCapabilityName) as Dictionary<string, object>;
if (optionsCapability == null)
{
throw new WebDriverException("Found " + this.optionsCapabilityName + " capability, but is not an object");
}

if (!options.ContainsKey("debuggerAddress"))
if (!optionsCapability.ContainsKey("debuggerAddress"))
{
throw new WebDriverException("Did not find debuggerAddress capability in " + this.optionsCapabilityName);
}

string debuggerAddress = options["debuggerAddress"].ToString();
string debuggerAddress = optionsCapability["debuggerAddress"].ToString();
try
{
DevToolsSession session = new DevToolsSession(debuggerAddress);
Task.Run(async () => await session.StartSession(devToolsProtocolVersion)).GetAwaiter().GetResult();
DevToolsSession session = new DevToolsSession(debuggerAddress, options);
Task.Run(async () => await session.StartSession()).GetAwaiter().GetResult();
this.devToolsSession = session;
}
catch (Exception e)
Expand All @@ -317,6 +316,17 @@ public DevToolsSession GetDevToolsSession(int devToolsProtocolVersion)
return this.devToolsSession;
}

/// <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>
[Obsolete("Use GetDevToolsSession(DevToolsOptions options)")]
public DevToolsSession GetDevToolsSession(int devToolsProtocolVersion)
{
return GetDevToolsSession(new DevToolsOptions() { ProtocolVersion = devToolsProtocolVersion });
}

/// <summary>
/// Closes a DevTools session.
/// </summary>
Expand Down
11 changes: 11 additions & 0 deletions dotnet/src/webdriver/DevTools/CommandResponseTypeMap.cs
Original file line number Diff line number Diff line change
Expand Up @@ -52,5 +52,16 @@ public bool TryGetCommandResponseType<T>(out Type commandResponseType)
{
return commandResponseTypeDictionary.TryGetValue(typeof(T), out commandResponseType);
}

/// <summary>
/// Gets the command response type corresponding to the specified command type.
/// </summary>
/// <param name="command">The type of command for which to retrieve the response type.</param>
/// <param name="commandResponseType">The returned response type.</param>
/// <returns><see langword="true"/> if the specified command type has a mapped response type; otherwise, <see langword="false"/>.</returns>
public bool TryGetCommandResponseType(ICommand command, out Type commandResponseType)
{
return commandResponseTypeDictionary.TryGetValue(command.GetType(), out commandResponseType);
}
}
}
37 changes: 37 additions & 0 deletions dotnet/src/webdriver/DevTools/DevToolsOptions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// <copyright file="DevToolsSession.cs" company="WebDriver Committers">
nvborisenko marked this conversation as resolved.
Show resolved Hide resolved
// 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>

namespace OpenQA.Selenium.DevTools
{
/// <summary>
/// Contains options configuring the DevTools session.
/// </summary>
public class DevToolsOptions
{
/// <summary>
/// Enables or disables waiting for the debugger when creating a new target.
/// If enabled, all targets will be halted until the runtime.runIfWaitingForDebugger is invoked.
nvborisenko marked this conversation as resolved.
Show resolved Hide resolved
/// </summary>
public bool WaitForDebuggerOnStart { get; set; }
/// <summary>
/// The specific version of the Developer Tools debugging protocol to use.
/// If left NULL the protocol version will be determined automatically based on the browser version.
/// </summary>
public int? ProtocolVersion { get; set; }
}
}
80 changes: 75 additions & 5 deletions dotnet/src/webdriver/DevTools/DevToolsSession.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,18 +54,29 @@ public class DevToolsSession : IDevToolsSession
private long currentCommandId = 0;

private DevToolsDomains domains;
private readonly DevToolsOptions options;

/// <summary>
/// Initializes a new instance of the DevToolsSession class, using the specified WebSocket endpoint.
/// </summary>
/// <param name="endpointAddress"></param>
public DevToolsSession(string endpointAddress)
[Obsolete("Use DevToolsSession(string endpointAddress, DevToolsOptions options)")]
public DevToolsSession(string endpointAddress) : this(endpointAddress, new DevToolsOptions()) { }

/// <summary>
/// Initializes a new instance of the DevToolsSession class, using the specified WebSocket endpoint and specified DevTools options.
/// </summary>
/// <param name="endpointAddress"></param>
/// <param name="options"></param>
/// <exception cref="ArgumentNullException"></exception>
public DevToolsSession(string endpointAddress, DevToolsOptions options)
{
if (string.IsNullOrWhiteSpace(endpointAddress))
{
throw new ArgumentNullException(nameof(endpointAddress));
}

this.options = options ?? throw new ArgumentNullException(nameof(options));
this.CommandTimeout = TimeSpan.FromSeconds(30);
this.debuggerEndpoint = endpointAddress;
if (endpointAddress.StartsWith("ws", StringComparison.InvariantCultureIgnoreCase))
Expand Down Expand Up @@ -149,6 +160,39 @@ public T GetVersionSpecificDomains<T>() where T : DevToolsSessionDomains
}

if (!this.domains.VersionSpecificDomains.ResponseTypeMap.TryGetCommandResponseType<TCommand>(out Type commandResponseType))
{
throw new InvalidOperationException($"Type {command.GetType()} does not correspond to a known command response type.");
}

return result.ToObject(commandResponseType) as ICommandResponse<TCommand>;
}

/// <summary>
/// Sends the specified command and returns the associated command response.
/// </summary>
/// <typeparam name="TCommand">A command object implementing the <see cref="ICommand"/> interface.</typeparam>
/// <param name="command">The command to be sent.</param>
/// <param name="sessionId">The target session of the command</param>
/// <param name="cancellationToken">A CancellationToken object to allow for cancellation of the command.</param>
/// <param name="millisecondsTimeout">The execution timeout of the command in milliseconds.</param>
/// <param name="throwExceptionIfResponseNotReceived"><see langword="true"/> to throw an exception if a response is not received; otherwise, <see langword="false"/>.</param>
/// <returns>The command response object implementing the <see cref="ICommandResponse{T}"/> interface.</returns>
public async Task<ICommandResponse<TCommand>> SendCommand<TCommand>(TCommand command, string sessionId, CancellationToken cancellationToken = default(CancellationToken), int? millisecondsTimeout = null, bool throwExceptionIfResponseNotReceived = true)
where TCommand : ICommand
{
if (command == null)
{
throw new ArgumentNullException(nameof(command));
}

var result = await SendCommand(command.CommandName, sessionId, JToken.FromObject(command), cancellationToken, millisecondsTimeout, throwExceptionIfResponseNotReceived).ConfigureAwait(false);

if (result == null)
{
return null;
}

if (!this.domains.VersionSpecificDomains.ResponseTypeMap.TryGetCommandResponseType(command, out Type commandResponseType))
{
throw new InvalidOperationException($"Type {typeof(TCommand)} does not correspond to a known command response type.");
}
Expand Down Expand Up @@ -195,7 +239,23 @@ public T GetVersionSpecificDomains<T>() where T : DevToolsSessionDomains
/// <param name="throwExceptionIfResponseNotReceived"><see langword="true"/> to throw an exception if a response is not received; otherwise, <see langword="false"/>.</param>
/// <returns>The command response object implementing the <see cref="ICommandResponse{T}"/> interface.</returns>
//[DebuggerStepThrough]
public async Task<JToken> SendCommand(string commandName, JToken commandParameters, CancellationToken cancellationToken = default(CancellationToken), int? millisecondsTimeout = null, bool throwExceptionIfResponseNotReceived = true)
public Task<JToken> SendCommand(string commandName, JToken commandParameters, CancellationToken cancellationToken = default(CancellationToken), int? millisecondsTimeout = null, bool throwExceptionIfResponseNotReceived = true)
{
return SendCommand(commandName, ActiveSessionId, commandParameters, cancellationToken, millisecondsTimeout, throwExceptionIfResponseNotReceived);
}

/// <summary>
/// Returns a JToken based on a command created with the specified command name and params.
/// </summary>
/// <param name="commandName">The name of the command to send.</param>
/// <param name="sessionId">The sessionId of the command.</param>
/// <param name="commandParameters">The parameters of the command as a JToken object</param>
/// <param name="cancellationToken">A CancellationToken object to allow for cancellation of the command.</param>
/// <param name="millisecondsTimeout">The execution timeout of the command in milliseconds.</param>
/// <param name="throwExceptionIfResponseNotReceived"><see langword="true"/> to throw an exception if a response is not received; otherwise, <see langword="false"/>.</param>
/// <returns>The command response object implementing the <see cref="ICommandResponse{T}"/> interface.</returns>
//[DebuggerStepThrough]
public async Task<JToken> SendCommand(string commandName, string sessionId, JToken commandParameters, CancellationToken cancellationToken = default(CancellationToken), int? millisecondsTimeout = null, bool throwExceptionIfResponseNotReceived = true)
{
if (millisecondsTimeout.HasValue == false)
{
Expand All @@ -208,7 +268,7 @@ public T GetVersionSpecificDomains<T>() where T : DevToolsSessionDomains
await this.InitializeSession().ConfigureAwait(false);
}

var message = new DevToolsCommandData(Interlocked.Increment(ref this.currentCommandId), this.ActiveSessionId, commandName, commandParameters);
var message = new DevToolsCommandData(Interlocked.Increment(ref this.currentCommandId), sessionId, commandName, commandParameters);

if (this.connection != null && this.connection.IsActive)
{
Expand Down Expand Up @@ -267,10 +327,10 @@ public void Dispose()
/// <summary>
/// Asynchronously starts the session.
/// </summary>
/// <param name="requestedProtocolVersion">The requested version of the protocol to use in communicating with the browswer.</param>
/// <returns>A task that represents the asynchronous operation.</returns>
internal async Task StartSession(int requestedProtocolVersion)
internal async Task StartSession()
{
var requestedProtocolVersion = options.ProtocolVersion.HasValue ? options.ProtocolVersion.Value : AutoDetectDevToolsProtocolVersion;
int protocolVersion = await InitializeProtocol(requestedProtocolVersion).ConfigureAwait(false);
this.domains = DevToolsDomains.InitializeDomains(protocolVersion, this);
await this.InitializeSocketConnection().ConfigureAwait(false);
Expand Down Expand Up @@ -399,6 +459,16 @@ private async Task InitializeSession()
await this.domains.Target.SetAutoAttach().ConfigureAwait(false);
LogTrace("AutoAttach is set.", this.attachedTargetId);

// The Target domain needs to send Sessionless commands! Else the waitForDebugger setting in setAutoAttach wont work!
if (options.WaitForDebuggerOnStart)
{
var setAutoAttachCommand = domains.Target.CreateSetAutoAttachCommand(true);
var setDiscoverTargetsCommand = domains.Target.CreateDiscoverTargetsCommand();

await SendCommand(setAutoAttachCommand, string.Empty, default(CancellationToken), null, true).ConfigureAwait(false);
await SendCommand(setDiscoverTargetsCommand, string.Empty, default(CancellationToken), null, true).ConfigureAwait(false);
}

this.domains.Target.TargetDetached += this.OnTargetDetached;
}

Expand Down
10 changes: 10 additions & 0 deletions dotnet/src/webdriver/DevTools/IDevTools.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
// limitations under the License.
// </copyright>

using System;

namespace OpenQA.Selenium.DevTools
{
/// <summary>
Expand All @@ -34,11 +36,19 @@ public interface IDevTools
/// <returns>The active session to use to communicate with the Developer Tools debugging protocol.</returns>
DevToolsSession GetDevToolsSession();

/// <summary>
/// Creates a session to communicate with a browser using a specific version of the Developer Tools debugging protocol.
/// </summary>
/// <param name="options">The options for the DevToolsSession to use.</param>
/// <returns>The active session to use to communicate with the Developer Tools debugging protocol.</returns>
DevToolsSession GetDevToolsSession(DevToolsOptions options);

/// <summary>
/// Creates a session to communicate with a browser using a specific version of the Developer Tools debugging protocol.
/// </summary>
/// <param name="protocolVersion">The specific version of the Developer Tools debugging protocol to use.</param>
/// <returns>The active session to use to communicate with the Developer Tools debugging protocol.</returns>
[Obsolete("Use GetDevToolsSession(DevToolsOptions options)")]
DevToolsSession GetDevToolsSession(int protocolVersion);

/// <summary>
Expand Down
4 changes: 4 additions & 0 deletions dotnet/src/webdriver/DevTools/Target.cs
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,10 @@ public abstract class Target
/// <returns>A task that represents the asynchronous operation.</returns>
public abstract Task SetAutoAttach();

internal abstract ICommand CreateSetAutoAttachCommand(bool waitForDebuggerOnStart);

internal abstract ICommand CreateDiscoverTargetsCommand();

/// <summary>
/// Raises the TargetDetached event.
/// </summary>
Expand Down
18 changes: 18 additions & 0 deletions dotnet/src/webdriver/DevTools/v118/V118Target.cs
Original file line number Diff line number Diff line change
Expand Up @@ -140,5 +140,23 @@ private void OnAttachedToTarget(object sender, AttachedToTargetEventArgs e)
WaitingForDebugger = e.WaitingForDebugger
});
}

internal override ICommand CreateSetAutoAttachCommand(bool waitForDebuggerOnStart)
{
return new SetAutoAttachCommandSettings
{
AutoAttach = true,
Flatten = true,
WaitForDebuggerOnStart = waitForDebuggerOnStart
};
}

internal override ICommand CreateDiscoverTargetsCommand()
{
return new SetDiscoverTargetsCommandSettings
{
Discover = true
};
}
}
}
18 changes: 18 additions & 0 deletions dotnet/src/webdriver/DevTools/v119/V119Target.cs
Original file line number Diff line number Diff line change
Expand Up @@ -140,5 +140,23 @@ private void OnAttachedToTarget(object sender, AttachedToTargetEventArgs e)
WaitingForDebugger = e.WaitingForDebugger
});
}

internal override ICommand CreateSetAutoAttachCommand(bool waitForDebuggerOnStart)
{
return new SetAutoAttachCommandSettings
{
AutoAttach = true,
Flatten = true,
WaitForDebuggerOnStart = waitForDebuggerOnStart
};
}

internal override ICommand CreateDiscoverTargetsCommand()
{
return new SetDiscoverTargetsCommandSettings
{
Discover = true
};
}
}
}
18 changes: 18 additions & 0 deletions dotnet/src/webdriver/DevTools/v120/V120Target.cs
Original file line number Diff line number Diff line change
Expand Up @@ -140,5 +140,23 @@ private void OnAttachedToTarget(object sender, AttachedToTargetEventArgs e)
WaitingForDebugger = e.WaitingForDebugger
});
}

internal override ICommand CreateSetAutoAttachCommand(bool waitForDebuggerOnStart)
{
return new SetAutoAttachCommandSettings
{
AutoAttach = true,
Flatten = true,
WaitForDebuggerOnStart = waitForDebuggerOnStart
};
}

internal override ICommand CreateDiscoverTargetsCommand()
{
return new SetDiscoverTargetsCommandSettings
{
Discover = true
};
}
}
}
18 changes: 18 additions & 0 deletions dotnet/src/webdriver/DevTools/v85/V85Target.cs
Original file line number Diff line number Diff line change
Expand Up @@ -135,5 +135,23 @@ private void OnAttachedToTarget(object sender, AttachedToTargetEventArgs e)
WaitingForDebugger = e.WaitingForDebugger
});
}

internal override ICommand CreateSetAutoAttachCommand(bool waitForDebuggerOnStart)
{
return new SetAutoAttachCommandSettings
{
AutoAttach = true,
Flatten = true,
WaitForDebuggerOnStart = waitForDebuggerOnStart
};
}

internal override ICommand CreateDiscoverTargetsCommand()
{
return new SetDiscoverTargetsCommandSettings
{
Discover = true
};
}
}
}
Loading