diff --git a/dotnet/src/webdriver/DevTools/DevToolsCommandSettings.cs b/dotnet/src/webdriver/DevTools/DevToolsCommandSettings.cs new file mode 100644 index 0000000000000..9cf74e5bfc432 --- /dev/null +++ b/dotnet/src/webdriver/DevTools/DevToolsCommandSettings.cs @@ -0,0 +1,38 @@ +// +// 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. +// + +using Newtonsoft.Json.Linq; + +namespace OpenQA.Selenium.DevTools +{ + public class DevToolsCommandSettings + { + public DevToolsCommandSettings(string commandName) + { + if (string.IsNullOrWhiteSpace(commandName)) + { + throw new ArgumentNullException(nameof(commandName)); + } + + CommandName = commandName; + } + public string SessionId { get; set; } + public string CommandName { get; set; } + public JToken CommandParameters { get; set; } + } +} diff --git a/dotnet/src/webdriver/DevTools/DevToolsSession.cs b/dotnet/src/webdriver/DevTools/DevToolsSession.cs index 311f4d74dbd4f..664eae19263b3 100644 --- a/dotnet/src/webdriver/DevTools/DevToolsSession.cs +++ b/dotnet/src/webdriver/DevTools/DevToolsSession.cs @@ -316,6 +316,90 @@ public T GetVersionSpecificDomains() where T : DevToolsSessionDomains return null; } + /// + /// Send a collection of and wait on all of their results. + /// + /// + /// + /// + /// + /// A list of command response object implementeing the interface. + public async Task> SendCommands(List commands, CancellationToken cancellationToken = default(CancellationToken), int? millisecondsTimeout = null, bool throwExceptionIfResponseNotReceived = true) + { + if (millisecondsTimeout.HasValue == false) + { + millisecondsTimeout = Convert.ToInt32(CommandTimeout.TotalMilliseconds); + } + + if (this.attachedTargetId == null) + { + LogTrace("Session not currently attached to a target; reattaching"); + await this.InitializeSession(); + } + + var messages = new List(); + foreach (var item in commands) + { + messages.Add(new DevToolsCommandData(Interlocked.Increment(ref this.currentCommandId), item.SessionId, item.CommandName, item.CommandParameters)); + } + + if (this.connection != null && this.connection.IsActive) + { + foreach (var message in messages) + { + var contents = JsonConvert.SerializeObject(message); + + this.pendingCommands.TryAdd(message.CommandId, message); + await this.connection.SendData(contents).ConfigureAwait(false); + } + + WaitHandle.WaitAll(messages.Select(x => x.SyncEvent.WaitHandle).ToArray(), millisecondsTimeout.Value); + + var noResponsesReceived = messages.Where(x => !x.SyncEvent.IsSet); + if (noResponsesReceived.Any() && throwExceptionIfResponseNotReceived) + { + throw new InvalidOperationException($"A command response was not received: {string.Join(", ", noResponsesReceived.Select(x => x.CommandName))}"); + } + + foreach (var message in messages) + { + DevToolsCommandData modified; + if (this.pendingCommands.TryRemove(message.CommandId, out modified)) + { + if (modified.IsError) + { + var errorMessage = modified.Result.Value("message"); + var errorData = modified.Result.Value("data"); + + var exceptionMessage = $"{message.CommandName}: {errorMessage}"; + if (!string.IsNullOrWhiteSpace(errorData)) + { + exceptionMessage = $"{exceptionMessage} - {errorData}"; + } + + LogTrace("Received Error Response {0}: {1} {2}", modified.CommandId, message, errorData); + throw new CommandResponseException(exceptionMessage) + { + Code = modified.Result.Value("code") + }; + } + } + } + + return messages.Select(x => new DevToolsCommandResponse + { + Result = x.Result, + SessionId = x.SessionId + }).ToList(); + } + else + { + LogTrace("WebSocket is not connected; not sending {0}", string.Join(", ", commands.Select(itm => itm.CommandName))); + } + + return null; + } + /// /// Releases all resources associated with this . /// diff --git a/dotnet/src/webdriver/DevTools/IDevToolsSession.cs b/dotnet/src/webdriver/DevTools/IDevToolsSession.cs index b9d1d06191eb4..dce714ed02ff8 100644 --- a/dotnet/src/webdriver/DevTools/IDevToolsSession.cs +++ b/dotnet/src/webdriver/DevTools/IDevToolsSession.cs @@ -85,5 +85,15 @@ Task SendCommand(TCommand command, /// to throw an exception if a response is not received; otherwise, . /// The command response object implementing the interface. Task SendCommand(string commandName, JToken @params, CancellationToken cancellationToken, int? millisecondsTimeout, bool throwExceptionIfResponseNotReceived); + + /// + /// Returns a collection of based on a collection of commands. + /// + /// + /// + /// + /// + /// A list of command response object implementeing the interface. + Task> SendCommands(List commands, CancellationToken cancellationToken = default(CancellationToken), int? millisecondsTimeout = null, bool throwExceptionIfResponseNotReceived = true); } }