diff --git a/dotnet/src/support/Events/EventFiringWebDriver.cs b/dotnet/src/support/Events/EventFiringWebDriver.cs index ac428cf2830b5..d866404c6ccdf 100644 --- a/dotnet/src/support/Events/EventFiringWebDriver.cs +++ b/dotnet/src/support/Events/EventFiringWebDriver.cs @@ -20,6 +20,7 @@ using System.Collections.Generic; using System.Collections.ObjectModel; using System.Drawing; +using System.Threading.Tasks; namespace OpenQA.Selenium.Support.Events { @@ -332,6 +333,17 @@ public INavigation Navigate() return new EventFiringNavigation(this); } + /// + /// Provides access to WebDriverBiDi events in script and log domains. + /// + /// An object allowing the user to access + /// WebDriverBiDi events. + [Obsolete("This method is in beta and may change in future releases.")] + public IScript Script() + { + throw new NotImplementedException(); + } + /// /// Instructs the driver to send future commands to a different frame or window. /// @@ -845,12 +857,21 @@ public EventFiringNavigation(EventFiringWebDriver driver) /// Move the browser back /// public void Back() + { + Task.Run(this.BackAsync).GetAwaiter().GetResult(); + } + + /// + /// Move the browser back as an asynchronous task. + /// + /// A task object representing the asynchronous operation + public async Task BackAsync() { try { WebDriverNavigationEventArgs e = new WebDriverNavigationEventArgs(this.parentDriver); this.parentDriver.OnNavigatingBack(e); - this.wrappedNavigation.Back(); + await this.wrappedNavigation.BackAsync().ConfigureAwait(false); this.parentDriver.OnNavigatedBack(e); } catch (Exception ex) @@ -861,15 +882,24 @@ public void Back() } /// - /// Move the browser forward + /// Move a single "item" forward in the browser's history. /// public void Forward() + { + Task.Run(this.ForwardAsync).GetAwaiter().GetResult(); + } + + /// + /// Move a single "item" forward in the browser's history as an asynchronous task. + /// + /// A task object representing the asynchronous operation. + public async Task ForwardAsync() { try { WebDriverNavigationEventArgs e = new WebDriverNavigationEventArgs(this.parentDriver); this.parentDriver.OnNavigatingForward(e); - this.wrappedNavigation.Forward(); + await this.wrappedNavigation.ForwardAsync().ConfigureAwait(false); this.parentDriver.OnNavigatedForward(e); } catch (Exception ex) @@ -880,16 +910,31 @@ public void Forward() } /// - /// Navigate to a url for your test + /// Navigate to a url. /// /// String of where you want the browser to go to public void GoToUrl(string url) { + Task.Run(() => this.GoToUrlAsync(url)).GetAwaiter().GetResult(); + } + + /// + /// Navigate to a url as an asynchronous task. + /// + /// String of where you want the browser to go. + /// A task object representing the asynchronous operation. + public async Task GoToUrlAsync(string url) + { + if (url == null) + { + throw new ArgumentNullException(nameof(url), "url cannot be null"); + } + try { WebDriverNavigationEventArgs e = new WebDriverNavigationEventArgs(this.parentDriver, url); this.parentDriver.OnNavigating(e); - this.wrappedNavigation.GoToUrl(url); + await this.wrappedNavigation.GoToUrlAsync(url).ConfigureAwait(false); this.parentDriver.OnNavigated(e); } catch (Exception ex) @@ -900,38 +945,46 @@ public void GoToUrl(string url) } /// - /// Navigate to a url for your test + /// Navigate to a url. /// /// Uri object of where you want the browser to go to public void GoToUrl(Uri url) + { + Task.Run(() => this.GoToUrlAsync(url)).GetAwaiter().GetResult(); + } + + /// + /// Navigate to a url as an asynchronous task. + /// + /// Uri object of where you want the browser to go. + /// A task object representing the asynchronous operation. + public async Task GoToUrlAsync(Uri url) { if (url == null) { throw new ArgumentNullException(nameof(url), "url cannot be null"); } - try - { - WebDriverNavigationEventArgs e = new WebDriverNavigationEventArgs(this.parentDriver, url.ToString()); - this.parentDriver.OnNavigating(e); - this.wrappedNavigation.GoToUrl(url); - this.parentDriver.OnNavigated(e); - } - catch (Exception ex) - { - this.parentDriver.OnException(new WebDriverExceptionEventArgs(this.parentDriver, ex)); - throw; - } + await this.GoToUrlAsync(url.ToString()).ConfigureAwait(false); } /// - /// Refresh the browser + /// Reload the current page. /// public void Refresh() + { + Task.Run(this.RefreshAsync).GetAwaiter().GetResult(); + } + + /// + /// Reload the current page as an asynchronous task. + /// + /// A task object representing the asynchronous operation. + public async Task RefreshAsync() { try { - this.wrappedNavigation.Refresh(); + await this.wrappedNavigation.RefreshAsync().ConfigureAwait(false); } catch (Exception ex) { diff --git a/dotnet/src/webdriver/ICommandExecutor.cs b/dotnet/src/webdriver/ICommandExecutor.cs index 68cfe200391f4..3bcfb520166d3 100644 --- a/dotnet/src/webdriver/ICommandExecutor.cs +++ b/dotnet/src/webdriver/ICommandExecutor.cs @@ -17,6 +17,7 @@ // using System; +using System.Threading.Tasks; namespace OpenQA.Selenium { @@ -39,5 +40,13 @@ public interface ICommandExecutor : IDisposable /// The command you wish to execute /// A response from the browser Response Execute(Command commandToExecute); + + + /// + /// Executes a command as an asynchronous task. + /// + /// The command you wish to execute + /// A task object representing the asynchronous operation + Task ExecuteAsync(Command commandToExecute); } } diff --git a/dotnet/src/webdriver/INavigation.cs b/dotnet/src/webdriver/INavigation.cs index bff75d4743e6b..a55b4dfda12bf 100644 --- a/dotnet/src/webdriver/INavigation.cs +++ b/dotnet/src/webdriver/INavigation.cs @@ -17,6 +17,7 @@ // using System; +using System.Threading.Tasks; namespace OpenQA.Selenium { @@ -31,12 +32,24 @@ public interface INavigation /// void Back(); + /// + /// Move back a single entry in the browser's history as an asynchronous task. + /// + /// A task object representing the asynchronous operation. + Task BackAsync(); + /// /// Move a single "item" forward in the browser's history. /// /// Does nothing if we are on the latest page viewed. void Forward(); + /// + /// Move a single "item" forward in the browser's history as an asynchronous task. + /// + /// A task object representing the asynchronous operation. + Task ForwardAsync(); + /// /// Load a new web page in the current browser window. /// @@ -52,6 +65,13 @@ public interface INavigation /// void GoToUrl(string url); + /// + /// Navigate to a url as an asynchronous task. + /// + /// String of where you want the browser to go. + /// A task object representing the asynchronous operation. + Task GoToUrlAsync(string url); + /// /// Load a new web page in the current browser window. /// @@ -67,9 +87,22 @@ public interface INavigation /// void GoToUrl(Uri url); + /// + /// Navigate to a url as an asynchronous task. + /// + /// Uri object of where you want the browser to go. + /// A task object representing the asynchronous operation. + Task GoToUrlAsync(Uri url); + /// /// Refreshes the current page. /// void Refresh(); + + /// + /// Reload the current page as an asynchronous task. + /// + /// A task object representing the asynchronous operation. + Task RefreshAsync(); } } diff --git a/dotnet/src/webdriver/IScript.cs b/dotnet/src/webdriver/IScript.cs new file mode 100644 index 0000000000000..a3b77aa23ef2f --- /dev/null +++ b/dotnet/src/webdriver/IScript.cs @@ -0,0 +1,57 @@ +// +// 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 System; +using System.Threading.Tasks; +using WebDriverBiDi.Log; + +namespace OpenQA.Selenium +{ + /// + /// Defines an interface providing access to WebDriverBiDi events in script and log domains. + /// + [Obsolete("This class is in beta and may change in future releases.")] + public interface IScript + { + /// + /// Add and remove handlers for console messages. + /// + [Obsolete("This event is in beta and may change in future releases.")] + event EventHandler ConsoleMessageHandler; + + /// + /// Add and remove handlers for console messages. + /// + [Obsolete("This event is in beta and may change in future releases.")] + event EventHandler JavaScriptErrorHandler; + + /// + /// Asynchronously starts monitoring for console and JavaScript log entries. + /// + /// A task object representing the asynchronous operation. + [Obsolete("This task is in beta and may change in future releases.")] + Task StartMonitoringLogEntries(); + + /// + /// Asynchronously stops monitoring for console and JavaScript log entries. + /// + /// A task object representing the asynchronous operation. + [Obsolete("This task is in beta and may change in future releases.")] + Task StopMonitoringLogEntries(); + } +} diff --git a/dotnet/src/webdriver/IWebDriver.cs b/dotnet/src/webdriver/IWebDriver.cs index 1f8d85a912691..f68cd72d1405a 100644 --- a/dotnet/src/webdriver/IWebDriver.cs +++ b/dotnet/src/webdriver/IWebDriver.cs @@ -115,6 +115,14 @@ public interface IWebDriver : ISearchContext, IDisposable /// the browser's history and to navigate to a given URL. INavigation Navigate(); + /// + /// Provides access to WebDriverBiDi events for Script and Logs. + /// + /// An object allowing the user to access + /// WebDriverBiDi events. + [Obsolete("This method is in beta and may change in future releases.")] + IScript Script(); + /// /// Instructs the driver to send future commands to a different frame or window. /// diff --git a/dotnet/src/webdriver/Navigator.cs b/dotnet/src/webdriver/Navigator.cs index f8f6047e91f5a..f61a5f4983f9b 100644 --- a/dotnet/src/webdriver/Navigator.cs +++ b/dotnet/src/webdriver/Navigator.cs @@ -18,6 +18,8 @@ using System; using System.Collections.Generic; +using System.Threading.Tasks; +using WebDriverBiDi.BrowsingContext; namespace OpenQA.Selenium { @@ -27,6 +29,14 @@ namespace OpenQA.Selenium internal class Navigator : INavigation { private WebDriver driver; + private string browsingContextId; + private static readonly Dictionary PageLoadStrategyMapper = new() + { + {"normal", ReadinessState.Complete}, + {"eager", ReadinessState.Interactive}, + {"none", ReadinessState.None} + }; + private ReadinessState readinessState; /// /// Initializes a new instance of the class @@ -35,64 +45,157 @@ internal class Navigator : INavigation public Navigator(WebDriver driver) { this.driver = driver; + // TODO: store the value of the current window's context id on the driver object + this.browsingContextId = driver.CurrentWindowHandle; + + string strategyCap = driver.Capabilities.GetCapability("pageLoadStrategy") as string; + this.readinessState = strategyCap == null ? ReadinessState.Complete : PageLoadStrategyMapper[strategyCap]; } /// - /// Move the browser back + /// Move back a single entry in the browser's history. /// public void Back() { - this.driver.InternalExecute(DriverCommand.GoBack, null); + Task.Run(this.BackAsync).GetAwaiter().GetResult(); + } + + /// + /// Move back a single entry in the browser's history as an asynchronous task. + /// + /// A task object representing the asynchronous operation. + public async Task BackAsync() + { + if (this.driver.BiDiDriver != null) + { + var traverseHistoryCommandParameters = + new TraverseHistoryCommandParameters(this.browsingContextId, -1); + await this.driver.BiDiDriver.BrowsingContext.TraverseHistoryAsync(traverseHistoryCommandParameters) + .ConfigureAwait(false); + } + else + { + await this.driver.InternalExecuteAsync(DriverCommand.GoBack, null).ConfigureAwait(false); + } } /// - /// Move the browser forward + /// Move a single "item" forward in the browser's history. /// public void Forward() { - this.driver.InternalExecute(DriverCommand.GoForward, null); + Task.Run(this.ForwardAsync).GetAwaiter().GetResult(); } /// - /// Navigate to a url for your test + /// Move a single "item" forward in the browser's history as an asynchronous task. + /// + /// A task object representing the asynchronous operation. + public async Task ForwardAsync() + { + if (this.driver.BiDiDriver != null) + { + var traverseHistoryCommandParameters = + new TraverseHistoryCommandParameters(this.browsingContextId, 1); + await this.driver.BiDiDriver.BrowsingContext.TraverseHistoryAsync(traverseHistoryCommandParameters) + .ConfigureAwait(false); + } + else + { + await this.driver.InternalExecuteAsync(DriverCommand.GoForward, null).ConfigureAwait(false); + } + } + + /// + /// Navigate to a url. /// /// String of where you want the browser to go to public void GoToUrl(string url) + { + Task.Run(() => this.GoToUrlAsync(url)).GetAwaiter().GetResult(); + } + + /// + /// Navigate to a url as an asynchronous task. + /// + /// String of where you want the browser to go. + /// A task object representing the asynchronous operation. + public async Task GoToUrlAsync(string url) { if (url == null) { throw new ArgumentNullException(nameof(url), "URL cannot be null."); } - Dictionary parameters = new Dictionary + if (this.driver.BiDiDriver != null) { - { "url", url } - }; - this.driver.InternalExecute(DriverCommand.Get, parameters); - + NavigateCommandParameters navigateCommandParameters = new NavigateCommandParameters(this.browsingContextId, url) + { + Wait = this.readinessState + }; + await driver.BiDiDriver.BrowsingContext.NavigateAsync(navigateCommandParameters).ConfigureAwait(false); + } + else + { + Dictionary parameters = new Dictionary + { + { "url", url } + }; + await this.driver.InternalExecuteAsync(DriverCommand.Get, parameters).ConfigureAwait(false); + } } /// - /// Navigate to a url for your test + /// Navigate to a url. /// - /// Uri object of where you want the browser to go to + /// Uri object of where you want the browser to go. public void GoToUrl(Uri url) + { + Task.Run(() => this.GoToUrlAsync(url)).GetAwaiter().GetResult(); + } + + /// + /// Navigate to a url as an asynchronous task. + /// + /// Uri object of where you want the browser to go. + /// A task object representing the asynchronous operation. + public async Task GoToUrlAsync(Uri url) { if (url == null) { throw new ArgumentNullException(nameof(url), "URL cannot be null."); } - this.GoToUrl(url.ToString()); + await this.GoToUrlAsync(url.ToString()).ConfigureAwait(false); } /// - /// Refresh the browser + /// Reload the current page. /// public void Refresh() { - // driver.SwitchTo().DefaultContent(); - this.driver.InternalExecute(DriverCommand.Refresh, null); + Task.Run(this.RefreshAsync).GetAwaiter().GetResult(); + } + + /// + /// Reload the current page as an asynchronous task. + /// + /// A task object representing the asynchronous operation. + public async Task RefreshAsync() + { + if (this.driver.BiDiDriver != null) + { + var reloadCommandParameters = + new ReloadCommandParameters(this.browsingContextId) + { + Wait = this.readinessState + }; + await this.driver.BiDiDriver.BrowsingContext.ReloadAsync(reloadCommandParameters).ConfigureAwait(false); + } + else + { + await this.driver.InternalExecuteAsync(DriverCommand.Refresh, null).ConfigureAwait(false); + } } } } diff --git a/dotnet/src/webdriver/Remote/DriverServiceCommandExecutor.cs b/dotnet/src/webdriver/Remote/DriverServiceCommandExecutor.cs index 4b0fcb27ed7fc..1e486ad96f98d 100644 --- a/dotnet/src/webdriver/Remote/DriverServiceCommandExecutor.cs +++ b/dotnet/src/webdriver/Remote/DriverServiceCommandExecutor.cs @@ -17,6 +17,7 @@ // using System; +using System.Threading.Tasks; namespace OpenQA.Selenium.Remote { @@ -92,6 +93,16 @@ public HttpCommandExecutor HttpExecutor /// The command you wish to execute /// A response from the browser public Response Execute(Command commandToExecute) + { + return Task.Run(() => this.ExecuteAsync(commandToExecute)).GetAwaiter().GetResult(); + } + + /// + /// Executes a command as an asynchronous task. + /// + /// The command you wish to execute + /// A task object representing the asynchronous operation + public async Task ExecuteAsync(Command commandToExecute) { if (commandToExecute == null) { @@ -108,7 +119,7 @@ public Response Execute(Command commandToExecute) // command, so that we can get the finally block. try { - toReturn = this.internalExecutor.Execute(commandToExecute); + toReturn = await this.internalExecutor.ExecuteAsync(commandToExecute).ConfigureAwait(false); } finally { diff --git a/dotnet/src/webdriver/Remote/HttpCommandExecutor.cs b/dotnet/src/webdriver/Remote/HttpCommandExecutor.cs index 3dbb5e4efd179..1872e418a7888 100644 --- a/dotnet/src/webdriver/Remote/HttpCommandExecutor.cs +++ b/dotnet/src/webdriver/Remote/HttpCommandExecutor.cs @@ -158,6 +158,16 @@ public bool TryAddCommand(string commandName, CommandInfo info) /// The command you wish to execute /// A response from the browser public virtual Response Execute(Command commandToExecute) + { + return Task.Run(() => this.ExecuteAsync(commandToExecute)).GetAwaiter().GetResult(); + } + + /// + /// Executes a command as an asynchronous task. + /// + /// The command you wish to execute + /// A task object representing the asynchronous operation + public virtual async Task ExecuteAsync(Command commandToExecute) { if (commandToExecute == null) { @@ -184,7 +194,7 @@ public virtual Response Execute(Command commandToExecute) HttpResponseInfo responseInfo = null; try { - responseInfo = Task.Run(async () => await this.MakeHttpRequest(requestInfo)).GetAwaiter().GetResult(); + responseInfo = await this.MakeHttpRequest(requestInfo).ConfigureAwait(false); } catch (HttpRequestException ex) { diff --git a/dotnet/src/webdriver/Script.cs b/dotnet/src/webdriver/Script.cs new file mode 100644 index 0000000000000..e96c86a2c1041 --- /dev/null +++ b/dotnet/src/webdriver/Script.cs @@ -0,0 +1,99 @@ +// +// 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 System; +using System.Threading.Tasks; +using WebDriverBiDi; +using WebDriverBiDi.Log; +using WebDriverBiDi.Session; + +namespace OpenQA.Selenium +{ + /// + /// Provides a mechanism to access WebDriverBiDi events in script and log domains. + /// + [Obsolete("This class is in beta and may change in future releases.")] + internal class Script : IScript + { + private readonly BiDiDriver biDiDriver; + + /// + /// Initializes a new instance of the class + /// + /// Driver in use + [Obsolete("This class is in beta and may change in future releases.")] + public Script(WebDriver driver) + { + this.biDiDriver = driver.BiDiDriver; + this.biDiDriver.Log.EntryAdded += OnEntryAdded; + } + + /// + /// Add and remove handlers for console messages. + /// + [Obsolete("This event is in beta and may change in future releases.")] + public event EventHandler ConsoleMessageHandler; + + /// + /// Add and remove handlers for console messages. + /// + [Obsolete("This event is in beta and may change in future releases.")] + public event EventHandler JavaScriptErrorHandler; + + /// + /// Asynchronously starts monitoring for console and JavaScript log entries. + /// + /// A task object representing the asynchronous operation. + [Obsolete("This task is in beta and may change in future releases.")] + public async Task StartMonitoringLogEntries() + { + SubscribeCommandParameters subscribe = new(); + subscribe.Events.Add("log.entryAdded"); + await biDiDriver.Session.SubscribeAsync(subscribe).ConfigureAwait(false); + } + + /// + /// Asynchronously stops monitoring for all console and JavaScript log entries. + /// + /// A task object representing the asynchronous operation. + [Obsolete("This task is in beta and may change in future releases.")] + public async Task StopMonitoringLogEntries() + { + UnsubscribeCommandParameters unsubscribe = new(); + unsubscribe.Events.Remove("log.entryAdded"); + await biDiDriver.Session.UnsubscribeAsync(unsubscribe).ConfigureAwait(false); + } + /// + /// Handles the EntryAdded event raised by the sender. + /// + /// The object that raised the event. + /// The event arguments containing information about the added entry. + private void OnEntryAdded(object? sender, EntryAddedEventArgs eventArgs) + { + if (eventArgs.Type == "javascript") + { + JavaScriptErrorHandler?.Invoke(this, eventArgs); + + } + else + { + ConsoleMessageHandler?.Invoke(this, eventArgs); + } + } + } +} diff --git a/dotnet/src/webdriver/WebDriver.cs b/dotnet/src/webdriver/WebDriver.cs index 3de43e25c452f..5f2dcee1f8cc4 100644 --- a/dotnet/src/webdriver/WebDriver.cs +++ b/dotnet/src/webdriver/WebDriver.cs @@ -24,6 +24,8 @@ using System.Collections.Generic; using System.Collections.ObjectModel; using System.Globalization; +using System.Threading.Tasks; +using WebDriverBiDi; namespace OpenQA.Selenium { @@ -45,6 +47,7 @@ public class WebDriver : IWebDriver, ISearchContext, IJavaScriptExecutor, IFinds private SessionId sessionId; private String authenticatorId; private List registeredCommands = new List(); + private BiDiDriver biDiDriver; /// /// Initializes a new instance of the class. @@ -188,6 +191,14 @@ public SessionId SessionId get { return this.sessionId; } } + /// + /// Gets the for the current session of this driver. + /// + internal BiDiDriver BiDiDriver + { + get { return this.biDiDriver; } + } + /// /// Gets or sets the responsible for detecting /// sequences of keystrokes representing file paths and names. @@ -444,6 +455,17 @@ public INavigation Navigate() return new Navigator(this); } + /// + /// Provides access to WebDriverBiDi events in script and log domains. + /// + /// An object allowing the user to access + /// WebDriverBiDi events. + [Obsolete("This method is in beta and may change in future releases.")] + public IScript Script() + { + return new Script(this); + } + /// /// Executes a command with this driver. /// @@ -556,7 +578,25 @@ internal ReadOnlyCollection GetElementsFromResponse(Response respon /// WebDriver Response internal Response InternalExecute(string driverCommandToExecute, Dictionary parameters) { - return this.Execute(driverCommandToExecute, parameters); + return Task.Run(() => this.InternalExecuteAsync(driverCommandToExecute, parameters)).GetAwaiter().GetResult(); + } + + /// + /// Executes commands with the driver asynchronously + /// + /// Command that needs executing + /// Parameters needed for the command + /// A task object representing the asynchronous operation + internal Task InternalExecuteAsync(string driverCommandToExecute, + Dictionary parameters) + { + return this.ExecuteAsync(driverCommandToExecute, parameters); + } + + internal Response Execute(string driverCommandToExecute, + Dictionary parameters) + { + return Task.Run(() => this.ExecuteAsync(driverCommandToExecute, parameters)).GetAwaiter().GetResult(); } /// @@ -565,7 +605,7 @@ internal Response InternalExecute(string driverCommandToExecute, DictionaryA value representing the command to execute. /// A containing the names and values of the parameters of the command. /// A containing information about the success or failure of the command and any data returned by the command. - protected virtual Response Execute(string driverCommandToExecute, Dictionary parameters) + protected virtual async Task ExecuteAsync(string driverCommandToExecute, Dictionary parameters) { Command commandToExecute = new Command(this.sessionId, driverCommandToExecute, parameters); @@ -573,7 +613,7 @@ protected virtual Response Execute(string driverCommandToExecute, Dictionary this.biDiDriver.StartAsync(webSocketUrl)).GetAwaiter().GetResult(); + } } /// diff --git a/dotnet/src/webdriver/WebDriver.csproj b/dotnet/src/webdriver/WebDriver.csproj index 354284b328755..31a76f5ee4123 100644 --- a/dotnet/src/webdriver/WebDriver.csproj +++ b/dotnet/src/webdriver/WebDriver.csproj @@ -53,6 +53,7 @@ + diff --git a/dotnet/test/common/BUILD.bazel b/dotnet/test/common/BUILD.bazel index a2ca040cbe9ce..2968ef73294e2 100644 --- a/dotnet/test/common/BUILD.bazel +++ b/dotnet/test/common/BUILD.bazel @@ -69,10 +69,13 @@ csharp_library( dotnet_nunit_test_suite( name = "AllTests", size = "large", - srcs = glob([ - "**/*Test.cs", - "**/*Tests.cs", - ]) + [ + srcs = glob( + [ + "**/*Test.cs", + "**/*Tests.cs", + ], + exclude = ["test/common/BiDi/*.cs"], + ) + [ ":assembly-fixtures", ], out = "WebDriver.Common.Tests", @@ -100,3 +103,39 @@ dotnet_nunit_test_suite( framework("nuget", "NUnit"), ], ) + +dotnet_bidi_test_suite( + name = "BiDiTests", + size = "large", + srcs = glob([ + "**/BiDi/*Test.cs", + ]) + [ + ":assembly-fixtures", + ], + out = "WebDriver.Common.Tests", + browsers = [ + # The first browser in this list is assumed to be the one that should + # be used by default. + "firefox", + "edge", + "chrome", + ], + data = [ + ":test-data", + ], + target_frameworks = ["net7.0"], + targeting_packs = [ + framework("nuget", "Microsoft.NETCore.App.Ref"), + ], + deps = [ + ":fixtures", + "//dotnet/src/webdriver", + framework("nuget", "BenderProxy"), + framework("nuget", "Newtonsoft.Json"), + framework("nuget", "NUnit"), + framework("nuget", "WebDriverBiDi"), + ], + env = { + "WEBDRIVER_BIDI": "true", + } +) diff --git a/dotnet/test/common/BiDi/LogsTest.cs b/dotnet/test/common/BiDi/LogsTest.cs new file mode 100644 index 0000000000000..ee9a784b5e527 --- /dev/null +++ b/dotnet/test/common/BiDi/LogsTest.cs @@ -0,0 +1,146 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using NUnit.Framework; +using OpenQA.Selenium.Environment; +using WebDriverBiDi.Log; + +namespace OpenQA.Selenium +{ + [TestFixture] + public class LogsTest : DriverTestFixture + { + [TearDown] + public void TearDownMethod() + { + driver.Script().StopMonitoringLogEntries(); + } + + [Test] + public async Task CanListenToConsoleLog() + { + EntryAddedEventArgs eventArgs = null; + driver.Script().ConsoleMessageHandler += (sender, args) => { eventArgs = args; }; + driver.Url = EnvironmentManager.Instance.UrlBuilder.WhereIs("bidi/logEntryAdded.html"); + + await driver.Script().StartMonitoringLogEntries(); + driver.FindElement(By.Id("consoleLog")).Click(); + + WaitFor(() => eventArgs != null, "Log messages are empty'"); + + Assert.That(eventArgs.Text, Is.EqualTo("Hello, world!")); + Assert.That(eventArgs.Type, Is.EqualTo("console")); + Assert.That(eventArgs.Arguments.Count, Is.EqualTo(1)); + Assert.That(eventArgs.Arguments[0].Type, Is.EqualTo("string")); + Assert.That(eventArgs.Method, Is.EqualTo("log")); + Assert.That(eventArgs.Level, Is.EqualTo(WebDriverBiDi.Log.LogLevel.Info)); + } + + [Test] + public async Task CanFilterConsoleLogs() + { + EntryAddedEventArgs eventArgs = null; + driver.Script().ConsoleMessageHandler += (sender, args) => + { + if (args.Level == WebDriverBiDi.Log.LogLevel.Error) + { + eventArgs = args; + } + }; + driver.Url = EnvironmentManager.Instance.UrlBuilder.WhereIs("bidi/logEntryAdded.html"); + + await driver.Script().StartMonitoringLogEntries(); + driver.FindElement(By.Id("consoleLog")).Click(); + driver.FindElement(By.Id("consoleError")).Click(); + + WaitFor(() => eventArgs != null, "Log messages are empty'"); + + Assert.That(eventArgs.Text, Is.EqualTo("I am console error")); + Assert.That(eventArgs.Type, Is.EqualTo("console")); + Assert.That(eventArgs.Arguments.Count, Is.EqualTo(1)); + Assert.That(eventArgs.Arguments[0].Type, Is.EqualTo("string")); + Assert.That(eventArgs.Method, Is.EqualTo("error")); + Assert.That(eventArgs.Level, Is.EqualTo(WebDriverBiDi.Log.LogLevel.Error)); + } + + [Test] + public async Task CanListenToJavaScriptLog() + { + EntryAddedEventArgs eventArgs = null; + driver.Script().JavaScriptErrorHandler += (sender, args) => + { + eventArgs = args; + }; + driver.Url = EnvironmentManager.Instance.UrlBuilder.WhereIs("bidi/logEntryAdded.html"); + + await driver.Script().StartMonitoringLogEntries(); + driver.FindElement(By.Id("jsException")).Click(); + + WaitFor(() => eventArgs != null, "Log messages are empty'"); + + Assert.That(eventArgs.Text, Is.EqualTo("Error: Not working")); + Assert.That(eventArgs.Type, Is.EqualTo("javascript")); + Assert.That(eventArgs.Level, Is.EqualTo(WebDriverBiDi.Log.LogLevel.Error)); + } + + [Test] + public async Task CanRetrieveStacktraceForALog() + { + EntryAddedEventArgs eventArgs = null; + driver.Script().JavaScriptErrorHandler += (sender, args) => + { + eventArgs = args; + }; + driver.Url = EnvironmentManager.Instance.UrlBuilder.WhereIs("bidi/logEntryAdded.html"); + + await driver.Script().StartMonitoringLogEntries(); + driver.FindElement(By.Id("logWithStacktrace")).Click(); + + WaitFor(() => eventArgs != null, "Log messages are empty'"); + + Assert.That(eventArgs.Text, Is.EqualTo("Error: Not working")); + Assert.That(eventArgs.Type, Is.EqualTo("javascript")); + Assert.That(eventArgs.Level, Is.EqualTo(WebDriverBiDi.Log.LogLevel.Error)); + Assert.That(eventArgs.StackTrace.CallFrames.Count, Is.GreaterThanOrEqualTo(3)); + } + + [Test] + public async Task CanListenToLogsWithMultipleConsumers() + { + List eventArgs = new List(); + driver.Script().ConsoleMessageHandler += (sender, args) => + { + eventArgs.Add(args); + }; + + driver.Script().ConsoleMessageHandler += (sender, args) => + { + eventArgs.Add(args); + }; + + driver.Script().JavaScriptErrorHandler += (sender, args) => + { + eventArgs.Add(args); + }; + + driver.Url = EnvironmentManager.Instance.UrlBuilder.WhereIs("bidi/logEntryAdded.html"); + + await driver.Script().StartMonitoringLogEntries(); + driver.FindElement(By.Id("consoleLog")).Click(); + driver.FindElement(By.Id("jsException")).Click(); + + WaitFor(() => eventArgs.Count == 3, "Log messages are empty'"); + + EntryAddedEventArgs event1 = eventArgs[0]; + EntryAddedEventArgs event2 = eventArgs[1]; + + Assert.That(event1.Text, Is.EqualTo("Hello, world!")); + Assert.That(event1.Type, Is.EqualTo("console")); + Assert.That(event1.Method, Is.EqualTo("log")); + Assert.That(event1.Level, Is.EqualTo(WebDriverBiDi.Log.LogLevel.Info)); + Assert.That(event2.Text, Is.EqualTo("Hello, world!")); + Assert.That(event2.Type, Is.EqualTo("console")); + Assert.That(event2.Method, Is.EqualTo("log")); + Assert.That(event2.Level, Is.EqualTo(WebDriverBiDi.Log.LogLevel.Info)); + } + } +} diff --git a/dotnet/test/common/Environment/DriverFactory.cs b/dotnet/test/common/Environment/DriverFactory.cs index 448dfc61d1a98..6139b3322ea2f 100644 --- a/dotnet/test/common/Environment/DriverFactory.cs +++ b/dotnet/test/common/Environment/DriverFactory.cs @@ -67,7 +67,6 @@ public IWebDriver CreateDriverWithOptions(Type driverType, DriverOptions driverO { browser = Browser.Chrome; options = GetDriverOptions(driverType, driverOptions); - options.UseWebSocketUrl = true; var chromeOptions = (ChromeOptions)options; chromeOptions.AddArguments("--no-sandbox", "--disable-dev-shm-usage"); @@ -185,6 +184,11 @@ protected void OnDriverLaunching(DriverService service, DriverOptions options) options.ImplicitWaitTimeout = overriddenOptions.ImplicitWaitTimeout; } + if (System.Environment.GetEnvironmentVariable("WEBDRIVER_BIDI") == "true") + { + options.UseWebSocketUrl = true; + } + return options; } diff --git a/dotnet/test/common/NavigationTest.cs b/dotnet/test/common/NavigationTest.cs index ee40894aa047c..482c1a28b2ee6 100644 --- a/dotnet/test/common/NavigationTest.cs +++ b/dotnet/test/common/NavigationTest.cs @@ -1,5 +1,6 @@ using NUnit.Framework; using System; +using WebDriverBiDi; namespace OpenQA.Selenium { @@ -12,10 +13,20 @@ public class NavigationTest : DriverTestFixture [NeedsFreshDriver(IsCreatedBeforeTest = true)] public void ShouldNotHaveProblemNavigatingWithNoPagesBrowsed() { - INavigation navigation; - navigation = driver.Navigate(); - navigation.Back(); - navigation.Forward(); + INavigation navigation = driver.Navigate(); + + if (((WebDriver)driver).Capabilities.HasCapability("webSocketUrl")) + { + var ex1 = Assert.Throws(() => navigation.Back()); + Assert.True(ex1!.Message.Contains("no such history entry")); + var ex2 = Assert.Throws(() => navigation.Forward()); + Assert.True(ex2!.Message.Contains("no such history entry")); + } + else + { + navigation.Back(); + navigation.Forward(); + } } [Test] @@ -40,7 +51,7 @@ public void ShouldAcceptInvalidUrlsUsingUris() INavigation navigation; navigation = driver.Navigate(); Assert.That(() => navigation.GoToUrl((Uri)null), Throws.InstanceOf()); - // new Uri("") and new Uri("isidsji30342??éåµñ©æ") + // new Uri("") and new Uri("isidsji30342??éåµñ©æ") // throw an exception, so we needn't worry about them. } @@ -89,6 +100,5 @@ public void ShouldRefreshPage() changedDiv = driver.FindElement(By.Id("dynamo")); Assert.AreEqual("What's for dinner?", changedDiv.Text); } - } } diff --git a/dotnet/test/common/StubDriver.cs b/dotnet/test/common/StubDriver.cs index dd981d9339a70..489d532a7c7c2 100644 --- a/dotnet/test/common/StubDriver.cs +++ b/dotnet/test/common/StubDriver.cs @@ -58,6 +58,11 @@ public INavigation Navigate() throw new NotImplementedException(); } + public IScript Script() + { + throw new NotImplementedException(); + } + public ITargetLocator SwitchTo() { throw new NotImplementedException(); diff --git a/dotnet/test/support/Events/EventFiringWebDriverTest.cs b/dotnet/test/support/Events/EventFiringWebDriverTest.cs index 972be725adfa9..3d59f056f3ee8 100644 --- a/dotnet/test/support/Events/EventFiringWebDriverTest.cs +++ b/dotnet/test/support/Events/EventFiringWebDriverTest.cs @@ -58,12 +58,15 @@ Navigated back Navigating forward Navigated forward "; + string normalizedExpectedLog = expectedLog.Replace("\r\n", "\n").Replace("\r", "\n"); mockDriver.VerifySet(x => x.Url = "http://www.get.com", Times.Once); mockDriver.Verify(x => x.Navigate(), Times.Exactly(3)); - mockNavigation.Verify(x => x.GoToUrl("http://www.navigate-to.com"), Times.Once); - mockNavigation.Verify(x => x.Back(), Times.Once); - mockNavigation.Verify(x => x.Forward(), Times.Once); - Assert.AreEqual(expectedLog, log.ToString()); + mockNavigation.Verify(x => x.GoToUrlAsync("http://www.navigate-to.com"), Times.Once); + mockNavigation.Verify(x => x.BackAsync(), Times.Once); + mockNavigation.Verify(x => x.ForwardAsync(), Times.Once); + + string normalizedActualLog = log.ToString().Replace("\r\n", "\n").Replace("\r", "\n"); + Assert.AreEqual(normalizedExpectedLog, normalizedActualLog); } [Test] diff --git a/third_party/dotnet/nuget/packages/webdriverbidi.0.0.1.nupkg b/third_party/dotnet/nuget/packages/webdriverbidi.0.0.1.nupkg new file mode 100644 index 0000000000000..99afa6c504946 Binary files /dev/null and b/third_party/dotnet/nuget/packages/webdriverbidi.0.0.1.nupkg differ