From 3e8b34cea24635e89aa42d09db8c37b6723a9005 Mon Sep 17 00:00:00 2001 From: Nikolay Borisenko <22616990+nvborisenko@users.noreply.github.com> Date: Mon, 9 Sep 2024 18:30:04 +0300 Subject: [PATCH] [dotnet] BiDi implementation (#14318) * Migrate * Use CLS compliant long instead of ulong * Use null instead of default for optional arguments * Use internal logging in websockettransport * Use internal logger in broker * Even with error log level * Simplify AsBidirectionalContextAsync * Fix ConfigureAwait in network module * Hide direct network interception * Rework public adding interception * Simplify network interception * Don't end session when disposing bidi driver --- dotnet/src/webdriver/BUILD.bazel | 4 + dotnet/src/webdriver/BiDi/BiDi.cs | 124 ++++++++ dotnet/src/webdriver/BiDi/BiDiException.cs | 10 + .../webdriver/BiDi/Communication/Broker.cs | 264 +++++++++++++++++ .../webdriver/BiDi/Communication/Command.cs | 67 +++++ .../BiDi/Communication/CommandOptions.cs | 8 + .../BiDi/Communication/EventHandler.cs | 39 +++ .../Converters/BrowserUserContextConverter.cs | 28 ++ .../Converters/BrowsingContextConverter.cs | 28 ++ .../Json/Converters/ChannelConverter.cs | 28 ++ .../Converters/DateTimeOffsetConverter.cs | 27 ++ .../Json/Converters/HandleConverter.cs | 28 ++ .../Json/Converters/InterceptConverter.cs | 28 ++ .../Json/Converters/InternalIdConverter.cs | 28 ++ .../Json/Converters/NavigationConverter.cs | 21 ++ .../Polymorphic/EvaluateResultConverter.cs | 27 ++ .../Polymorphic/LogEntryConverter.cs | 27 ++ .../Polymorphic/MessageConverter.cs | 27 ++ .../Polymorphic/RealmInfoConverter.cs | 33 +++ .../Polymorphic/RemoteValueConverter.cs | 48 ++++ .../Json/Converters/PreloadScriptConverter.cs | 28 ++ .../Json/Converters/RealmConverter.cs | 28 ++ .../Json/Converters/RealmTypeConverter.cs | 32 +++ .../Json/Converters/RequestConverter.cs | 28 ++ .../webdriver/BiDi/Communication/Message.cs | 21 ++ .../Communication/Transport/ITransport.cs | 15 + .../Transport/WebSocketTransport.cs | 66 +++++ dotnet/src/webdriver/BiDi/EventArgs.cs | 13 + .../BiDi/Modules/Browser/BrowserModule.cs | 32 +++ .../BiDi/Modules/Browser/CloseCommand.cs | 7 + .../Browser/CreateUserContextCommand.cs | 7 + .../Modules/Browser/GetUserContextsCommand.cs | 10 + .../Browser/RemoveUserContextCommand.cs | 9 + .../BiDi/Modules/Browser/UserContext.cs | 27 ++ .../BiDi/Modules/Browser/UserContextInfo.cs | 3 + .../BrowsingContext/ActivateCommand.cs | 9 + .../BrowsingContext/BrowsingContext.cs | 184 ++++++++++++ .../BrowsingContext/BrowsingContextInfo.cs | 12 + .../BrowsingContextInputModule.cs | 17 ++ .../BrowsingContextLogModule.cs | 30 ++ .../BrowsingContext/BrowsingContextModule.cs | 270 ++++++++++++++++++ .../BrowsingContextNetworkModule.cs | 92 ++++++ .../BrowsingContextScriptModule.cs | 57 ++++ .../BrowsingContextStorageModule.cs | 38 +++ .../CaptureScreenshotCommand.cs | 52 ++++ .../Modules/BrowsingContext/CloseCommand.cs | 9 + .../Modules/BrowsingContext/CreateCommand.cs | 31 ++ .../Modules/BrowsingContext/GetTreeCommand.cs | 22 ++ .../HandleUserPromptCommand.cs | 19 ++ .../BrowsingContext/LocateNodesCommand.cs | 26 ++ .../BiDi/Modules/BrowsingContext/Locator.cs | 49 ++++ .../BrowsingContext/NavigateCommand.cs | 24 ++ .../Modules/BrowsingContext/Navigation.cs | 3 + .../Modules/BrowsingContext/NavigationInfo.cs | 6 + .../Modules/BrowsingContext/PrintCommand.cs | 68 +++++ .../Modules/BrowsingContext/ReloadCommand.cs | 19 ++ .../BrowsingContext/SetViewportCommand.cs | 21 ++ .../BrowsingContext/TraverseHistoryCommand.cs | 11 + .../UserPromptClosedEventArgs.cs | 10 + .../UserPromptOpenedEventArgs.cs | 18 ++ .../BiDi/Modules/Input/InputModule.cs | 26 ++ .../Modules/Input/PerformActionsCommand.cs | 74 +++++ .../Modules/Input/ReleaseActionsCommand.cs | 9 + .../webdriver/BiDi/Modules/Log/LogEntry.cs | 25 ++ .../webdriver/BiDi/Modules/Log/LogModule.cs | 18 ++ dotnet/src/webdriver/BiDi/Modules/Module.cs | 8 + .../Modules/Network/AddInterceptCommand.cs | 29 ++ .../BiDi/Modules/Network/AuthChallenge.cs | 3 + .../BiDi/Modules/Network/AuthCredentials.cs | 12 + .../Modules/Network/AuthRequiredEventArgs.cs | 6 + .../Network/BaseParametersEventArgs.cs | 13 + .../Network/BeforeRequestSentEventArgs.cs | 7 + .../BiDi/Modules/Network/BytesValue.cs | 15 + .../Modules/Network/ContinueRequestCommand.cs | 32 +++ .../Network/ContinueResponseCommand.cs | 32 +++ .../Network/ContinueWithAuthCommand.cs | 24 ++ .../webdriver/BiDi/Modules/Network/Cookie.cs | 17 ++ .../BiDi/Modules/Network/CookieHeader.cs | 3 + .../Modules/Network/FailRequestCommand.cs | 9 + .../Modules/Network/FetchErrorEventArgs.cs | 7 + .../BiDi/Modules/Network/FetchTimingInfo.cs | 15 + .../webdriver/BiDi/Modules/Network/Header.cs | 3 + .../BiDi/Modules/Network/Initiator.cs | 20 ++ .../BiDi/Modules/Network/Intercept.cs | 105 +++++++ .../BiDi/Modules/Network/NetworkModule.cs | 173 +++++++++++ .../Modules/Network/ProvideResponseCommand.cs | 32 +++ .../Modules/Network/RemoveInterceptCommand.cs | 9 + .../webdriver/BiDi/Modules/Network/Request.cs | 51 ++++ .../BiDi/Modules/Network/RequestData.cs | 5 + .../Network/ResponseCompletedEventArgs.cs | 7 + .../BiDi/Modules/Network/ResponseContent.cs | 3 + .../BiDi/Modules/Network/ResponseData.cs | 20 ++ .../Network/ResponseStartedEventArgs.cs | 7 + .../BiDi/Modules/Network/SetCookieHeader.cs | 18 ++ .../BiDi/Modules/Network/UrlPattern.cs | 31 ++ .../Modules/Script/AddPreloadScriptCommand.cs | 26 ++ .../Modules/Script/CallFunctionCommand.cs | 32 +++ .../webdriver/BiDi/Modules/Script/Channel.cs | 14 + .../BiDi/Modules/Script/ChannelValue.cs | 13 + .../BiDi/Modules/Script/DisownCommand.cs | 8 + .../BiDi/Modules/Script/EvaluateCommand.cs | 35 +++ .../BiDi/Modules/Script/GetRealmsCommand.cs | 22 ++ .../webdriver/BiDi/Modules/Script/Handle.cs | 14 + .../BiDi/Modules/Script/InternalId.cs | 14 + .../BiDi/Modules/Script/LocalValue.cs | 83 ++++++ .../BiDi/Modules/Script/NodeProperties.cs | 28 ++ .../BiDi/Modules/Script/PreloadScript.cs | 27 ++ .../webdriver/BiDi/Modules/Script/Realm.cs | 14 + .../BiDi/Modules/Script/RealmInfo.cs | 36 +++ .../BiDi/Modules/Script/RealmType.cs | 13 + .../BiDi/Modules/Script/RemoteReference.cs | 13 + .../BiDi/Modules/Script/RemoteValue.cs | 243 ++++++++++++++++ .../Script/RemovePreloadScriptCommand.cs | 9 + .../BiDi/Modules/Script/ResultOwnership.cs | 7 + .../Modules/Script/ScriptEvaluateException.cs | 14 + .../BiDi/Modules/Script/ScriptModule.cs | 91 ++++++ .../Modules/Script/SerializationOptions.cs | 17 ++ .../webdriver/BiDi/Modules/Script/Source.cs | 6 + .../BiDi/Modules/Script/StackFrame.cs | 3 + .../BiDi/Modules/Script/StackTrace.cs | 5 + .../webdriver/BiDi/Modules/Script/Target.cs | 19 ++ .../Modules/Session/CapabilitiesRequest.cs | 10 + .../BiDi/Modules/Session/CapabilityRequest.cs | 16 ++ .../BiDi/Modules/Session/EndCommand.cs | 7 + .../BiDi/Modules/Session/NewCommand.cs | 18 ++ .../Modules/Session/ProxyConfiguration.cs | 32 +++ .../BiDi/Modules/Session/SessionModule.cs | 49 ++++ .../BiDi/Modules/Session/StatusCommand.cs | 9 + .../BiDi/Modules/Session/SubscribeCommand.cs | 16 ++ .../Modules/Session/UnsubscribeCommand.cs | 7 + .../Modules/Storage/DeleteCookiesCommand.cs | 16 ++ .../BiDi/Modules/Storage/GetCookiesCommand.cs | 59 ++++ .../BiDi/Modules/Storage/PartitionKey.cs | 8 + .../BiDi/Modules/Storage/SetCookieCommand.cs | 31 ++ .../BiDi/Modules/Storage/StorageModule.cs | 45 +++ .../BiDi/Properties/IsExternalInit.cs | 3 + dotnet/src/webdriver/BiDi/Subscription.cs | 43 +++ .../webdriver/BiDi/WebDriver.Extensions.cs | 27 ++ 138 files changed, 4387 insertions(+) create mode 100644 dotnet/src/webdriver/BiDi/BiDi.cs create mode 100644 dotnet/src/webdriver/BiDi/BiDiException.cs create mode 100644 dotnet/src/webdriver/BiDi/Communication/Broker.cs create mode 100644 dotnet/src/webdriver/BiDi/Communication/Command.cs create mode 100644 dotnet/src/webdriver/BiDi/Communication/CommandOptions.cs create mode 100644 dotnet/src/webdriver/BiDi/Communication/EventHandler.cs create mode 100644 dotnet/src/webdriver/BiDi/Communication/Json/Converters/BrowserUserContextConverter.cs create mode 100644 dotnet/src/webdriver/BiDi/Communication/Json/Converters/BrowsingContextConverter.cs create mode 100644 dotnet/src/webdriver/BiDi/Communication/Json/Converters/ChannelConverter.cs create mode 100644 dotnet/src/webdriver/BiDi/Communication/Json/Converters/DateTimeOffsetConverter.cs create mode 100644 dotnet/src/webdriver/BiDi/Communication/Json/Converters/HandleConverter.cs create mode 100644 dotnet/src/webdriver/BiDi/Communication/Json/Converters/InterceptConverter.cs create mode 100644 dotnet/src/webdriver/BiDi/Communication/Json/Converters/InternalIdConverter.cs create mode 100644 dotnet/src/webdriver/BiDi/Communication/Json/Converters/NavigationConverter.cs create mode 100644 dotnet/src/webdriver/BiDi/Communication/Json/Converters/Polymorphic/EvaluateResultConverter.cs create mode 100644 dotnet/src/webdriver/BiDi/Communication/Json/Converters/Polymorphic/LogEntryConverter.cs create mode 100644 dotnet/src/webdriver/BiDi/Communication/Json/Converters/Polymorphic/MessageConverter.cs create mode 100644 dotnet/src/webdriver/BiDi/Communication/Json/Converters/Polymorphic/RealmInfoConverter.cs create mode 100644 dotnet/src/webdriver/BiDi/Communication/Json/Converters/Polymorphic/RemoteValueConverter.cs create mode 100644 dotnet/src/webdriver/BiDi/Communication/Json/Converters/PreloadScriptConverter.cs create mode 100644 dotnet/src/webdriver/BiDi/Communication/Json/Converters/RealmConverter.cs create mode 100644 dotnet/src/webdriver/BiDi/Communication/Json/Converters/RealmTypeConverter.cs create mode 100644 dotnet/src/webdriver/BiDi/Communication/Json/Converters/RequestConverter.cs create mode 100644 dotnet/src/webdriver/BiDi/Communication/Message.cs create mode 100644 dotnet/src/webdriver/BiDi/Communication/Transport/ITransport.cs create mode 100644 dotnet/src/webdriver/BiDi/Communication/Transport/WebSocketTransport.cs create mode 100644 dotnet/src/webdriver/BiDi/EventArgs.cs create mode 100644 dotnet/src/webdriver/BiDi/Modules/Browser/BrowserModule.cs create mode 100644 dotnet/src/webdriver/BiDi/Modules/Browser/CloseCommand.cs create mode 100644 dotnet/src/webdriver/BiDi/Modules/Browser/CreateUserContextCommand.cs create mode 100644 dotnet/src/webdriver/BiDi/Modules/Browser/GetUserContextsCommand.cs create mode 100644 dotnet/src/webdriver/BiDi/Modules/Browser/RemoveUserContextCommand.cs create mode 100644 dotnet/src/webdriver/BiDi/Modules/Browser/UserContext.cs create mode 100644 dotnet/src/webdriver/BiDi/Modules/Browser/UserContextInfo.cs create mode 100644 dotnet/src/webdriver/BiDi/Modules/BrowsingContext/ActivateCommand.cs create mode 100644 dotnet/src/webdriver/BiDi/Modules/BrowsingContext/BrowsingContext.cs create mode 100644 dotnet/src/webdriver/BiDi/Modules/BrowsingContext/BrowsingContextInfo.cs create mode 100644 dotnet/src/webdriver/BiDi/Modules/BrowsingContext/BrowsingContextInputModule.cs create mode 100644 dotnet/src/webdriver/BiDi/Modules/BrowsingContext/BrowsingContextLogModule.cs create mode 100644 dotnet/src/webdriver/BiDi/Modules/BrowsingContext/BrowsingContextModule.cs create mode 100644 dotnet/src/webdriver/BiDi/Modules/BrowsingContext/BrowsingContextNetworkModule.cs create mode 100644 dotnet/src/webdriver/BiDi/Modules/BrowsingContext/BrowsingContextScriptModule.cs create mode 100644 dotnet/src/webdriver/BiDi/Modules/BrowsingContext/BrowsingContextStorageModule.cs create mode 100644 dotnet/src/webdriver/BiDi/Modules/BrowsingContext/CaptureScreenshotCommand.cs create mode 100644 dotnet/src/webdriver/BiDi/Modules/BrowsingContext/CloseCommand.cs create mode 100644 dotnet/src/webdriver/BiDi/Modules/BrowsingContext/CreateCommand.cs create mode 100644 dotnet/src/webdriver/BiDi/Modules/BrowsingContext/GetTreeCommand.cs create mode 100644 dotnet/src/webdriver/BiDi/Modules/BrowsingContext/HandleUserPromptCommand.cs create mode 100644 dotnet/src/webdriver/BiDi/Modules/BrowsingContext/LocateNodesCommand.cs create mode 100644 dotnet/src/webdriver/BiDi/Modules/BrowsingContext/Locator.cs create mode 100644 dotnet/src/webdriver/BiDi/Modules/BrowsingContext/NavigateCommand.cs create mode 100644 dotnet/src/webdriver/BiDi/Modules/BrowsingContext/Navigation.cs create mode 100644 dotnet/src/webdriver/BiDi/Modules/BrowsingContext/NavigationInfo.cs create mode 100644 dotnet/src/webdriver/BiDi/Modules/BrowsingContext/PrintCommand.cs create mode 100644 dotnet/src/webdriver/BiDi/Modules/BrowsingContext/ReloadCommand.cs create mode 100644 dotnet/src/webdriver/BiDi/Modules/BrowsingContext/SetViewportCommand.cs create mode 100644 dotnet/src/webdriver/BiDi/Modules/BrowsingContext/TraverseHistoryCommand.cs create mode 100644 dotnet/src/webdriver/BiDi/Modules/BrowsingContext/UserPromptClosedEventArgs.cs create mode 100644 dotnet/src/webdriver/BiDi/Modules/BrowsingContext/UserPromptOpenedEventArgs.cs create mode 100644 dotnet/src/webdriver/BiDi/Modules/Input/InputModule.cs create mode 100644 dotnet/src/webdriver/BiDi/Modules/Input/PerformActionsCommand.cs create mode 100644 dotnet/src/webdriver/BiDi/Modules/Input/ReleaseActionsCommand.cs create mode 100644 dotnet/src/webdriver/BiDi/Modules/Log/LogEntry.cs create mode 100644 dotnet/src/webdriver/BiDi/Modules/Log/LogModule.cs create mode 100644 dotnet/src/webdriver/BiDi/Modules/Module.cs create mode 100644 dotnet/src/webdriver/BiDi/Modules/Network/AddInterceptCommand.cs create mode 100644 dotnet/src/webdriver/BiDi/Modules/Network/AuthChallenge.cs create mode 100644 dotnet/src/webdriver/BiDi/Modules/Network/AuthCredentials.cs create mode 100644 dotnet/src/webdriver/BiDi/Modules/Network/AuthRequiredEventArgs.cs create mode 100644 dotnet/src/webdriver/BiDi/Modules/Network/BaseParametersEventArgs.cs create mode 100644 dotnet/src/webdriver/BiDi/Modules/Network/BeforeRequestSentEventArgs.cs create mode 100644 dotnet/src/webdriver/BiDi/Modules/Network/BytesValue.cs create mode 100644 dotnet/src/webdriver/BiDi/Modules/Network/ContinueRequestCommand.cs create mode 100644 dotnet/src/webdriver/BiDi/Modules/Network/ContinueResponseCommand.cs create mode 100644 dotnet/src/webdriver/BiDi/Modules/Network/ContinueWithAuthCommand.cs create mode 100644 dotnet/src/webdriver/BiDi/Modules/Network/Cookie.cs create mode 100644 dotnet/src/webdriver/BiDi/Modules/Network/CookieHeader.cs create mode 100644 dotnet/src/webdriver/BiDi/Modules/Network/FailRequestCommand.cs create mode 100644 dotnet/src/webdriver/BiDi/Modules/Network/FetchErrorEventArgs.cs create mode 100644 dotnet/src/webdriver/BiDi/Modules/Network/FetchTimingInfo.cs create mode 100644 dotnet/src/webdriver/BiDi/Modules/Network/Header.cs create mode 100644 dotnet/src/webdriver/BiDi/Modules/Network/Initiator.cs create mode 100644 dotnet/src/webdriver/BiDi/Modules/Network/Intercept.cs create mode 100644 dotnet/src/webdriver/BiDi/Modules/Network/NetworkModule.cs create mode 100644 dotnet/src/webdriver/BiDi/Modules/Network/ProvideResponseCommand.cs create mode 100644 dotnet/src/webdriver/BiDi/Modules/Network/RemoveInterceptCommand.cs create mode 100644 dotnet/src/webdriver/BiDi/Modules/Network/Request.cs create mode 100644 dotnet/src/webdriver/BiDi/Modules/Network/RequestData.cs create mode 100644 dotnet/src/webdriver/BiDi/Modules/Network/ResponseCompletedEventArgs.cs create mode 100644 dotnet/src/webdriver/BiDi/Modules/Network/ResponseContent.cs create mode 100644 dotnet/src/webdriver/BiDi/Modules/Network/ResponseData.cs create mode 100644 dotnet/src/webdriver/BiDi/Modules/Network/ResponseStartedEventArgs.cs create mode 100644 dotnet/src/webdriver/BiDi/Modules/Network/SetCookieHeader.cs create mode 100644 dotnet/src/webdriver/BiDi/Modules/Network/UrlPattern.cs create mode 100644 dotnet/src/webdriver/BiDi/Modules/Script/AddPreloadScriptCommand.cs create mode 100644 dotnet/src/webdriver/BiDi/Modules/Script/CallFunctionCommand.cs create mode 100644 dotnet/src/webdriver/BiDi/Modules/Script/Channel.cs create mode 100644 dotnet/src/webdriver/BiDi/Modules/Script/ChannelValue.cs create mode 100644 dotnet/src/webdriver/BiDi/Modules/Script/DisownCommand.cs create mode 100644 dotnet/src/webdriver/BiDi/Modules/Script/EvaluateCommand.cs create mode 100644 dotnet/src/webdriver/BiDi/Modules/Script/GetRealmsCommand.cs create mode 100644 dotnet/src/webdriver/BiDi/Modules/Script/Handle.cs create mode 100644 dotnet/src/webdriver/BiDi/Modules/Script/InternalId.cs create mode 100644 dotnet/src/webdriver/BiDi/Modules/Script/LocalValue.cs create mode 100644 dotnet/src/webdriver/BiDi/Modules/Script/NodeProperties.cs create mode 100644 dotnet/src/webdriver/BiDi/Modules/Script/PreloadScript.cs create mode 100644 dotnet/src/webdriver/BiDi/Modules/Script/Realm.cs create mode 100644 dotnet/src/webdriver/BiDi/Modules/Script/RealmInfo.cs create mode 100644 dotnet/src/webdriver/BiDi/Modules/Script/RealmType.cs create mode 100644 dotnet/src/webdriver/BiDi/Modules/Script/RemoteReference.cs create mode 100644 dotnet/src/webdriver/BiDi/Modules/Script/RemoteValue.cs create mode 100644 dotnet/src/webdriver/BiDi/Modules/Script/RemovePreloadScriptCommand.cs create mode 100644 dotnet/src/webdriver/BiDi/Modules/Script/ResultOwnership.cs create mode 100644 dotnet/src/webdriver/BiDi/Modules/Script/ScriptEvaluateException.cs create mode 100644 dotnet/src/webdriver/BiDi/Modules/Script/ScriptModule.cs create mode 100644 dotnet/src/webdriver/BiDi/Modules/Script/SerializationOptions.cs create mode 100644 dotnet/src/webdriver/BiDi/Modules/Script/Source.cs create mode 100644 dotnet/src/webdriver/BiDi/Modules/Script/StackFrame.cs create mode 100644 dotnet/src/webdriver/BiDi/Modules/Script/StackTrace.cs create mode 100644 dotnet/src/webdriver/BiDi/Modules/Script/Target.cs create mode 100644 dotnet/src/webdriver/BiDi/Modules/Session/CapabilitiesRequest.cs create mode 100644 dotnet/src/webdriver/BiDi/Modules/Session/CapabilityRequest.cs create mode 100644 dotnet/src/webdriver/BiDi/Modules/Session/EndCommand.cs create mode 100644 dotnet/src/webdriver/BiDi/Modules/Session/NewCommand.cs create mode 100644 dotnet/src/webdriver/BiDi/Modules/Session/ProxyConfiguration.cs create mode 100644 dotnet/src/webdriver/BiDi/Modules/Session/SessionModule.cs create mode 100644 dotnet/src/webdriver/BiDi/Modules/Session/StatusCommand.cs create mode 100644 dotnet/src/webdriver/BiDi/Modules/Session/SubscribeCommand.cs create mode 100644 dotnet/src/webdriver/BiDi/Modules/Session/UnsubscribeCommand.cs create mode 100644 dotnet/src/webdriver/BiDi/Modules/Storage/DeleteCookiesCommand.cs create mode 100644 dotnet/src/webdriver/BiDi/Modules/Storage/GetCookiesCommand.cs create mode 100644 dotnet/src/webdriver/BiDi/Modules/Storage/PartitionKey.cs create mode 100644 dotnet/src/webdriver/BiDi/Modules/Storage/SetCookieCommand.cs create mode 100644 dotnet/src/webdriver/BiDi/Modules/Storage/StorageModule.cs create mode 100644 dotnet/src/webdriver/BiDi/Properties/IsExternalInit.cs create mode 100644 dotnet/src/webdriver/BiDi/Subscription.cs create mode 100644 dotnet/src/webdriver/BiDi/WebDriver.Extensions.cs diff --git a/dotnet/src/webdriver/BUILD.bazel b/dotnet/src/webdriver/BUILD.bazel index 2e1c41bfb56df..dd7f9237b5094 100644 --- a/dotnet/src/webdriver/BUILD.bazel +++ b/dotnet/src/webdriver/BUILD.bazel @@ -52,6 +52,8 @@ csharp_library( ], deps = [ framework("nuget", "NETStandard.Library"), + framework("nuget", "Microsoft.Bcl.AsyncInterfaces"), + framework("nuget", "System.Threading.Tasks.Extensions"), framework("nuget", "System.Memory"), framework("nuget", "System.Text.Json"), ], @@ -111,6 +113,8 @@ csharp_library( ], deps = [ framework("nuget", "NETStandard.Library"), + framework("nuget", "Microsoft.Bcl.AsyncInterfaces"), + framework("nuget", "System.Threading.Tasks.Extensions"), framework("nuget", "System.Memory"), framework("nuget", "System.Text.Json"), ], diff --git a/dotnet/src/webdriver/BiDi/BiDi.cs b/dotnet/src/webdriver/BiDi/BiDi.cs new file mode 100644 index 0000000000000..f7acc0640b5a0 --- /dev/null +++ b/dotnet/src/webdriver/BiDi/BiDi.cs @@ -0,0 +1,124 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using OpenQA.Selenium.BiDi.Communication; +using OpenQA.Selenium.BiDi.Communication.Transport; + +namespace OpenQA.Selenium.BiDi; + +public class BiDi : IAsyncDisposable +{ + private readonly ITransport _transport; + private readonly Broker _broker; + + private readonly Lazy _sessionModule; + private readonly Lazy _browsingContextModule; + private readonly Lazy _browserModule; + private readonly Lazy _networkModule; + private readonly Lazy _inputModule; + private readonly Lazy _scriptModule; + private readonly Lazy _logModule; + private readonly Lazy _storageModule; + + internal BiDi(string url) + { + var uri = new Uri(url); + + _transport = new WebSocketTransport(new Uri(url)); + _broker = new Broker(this, _transport); + + _sessionModule = new Lazy(() => new Modules.Session.SessionModule(_broker)); + _browsingContextModule = new Lazy(() => new Modules.BrowsingContext.BrowsingContextModule(_broker)); + _browserModule = new Lazy(() => new Modules.Browser.BrowserModule(_broker)); + _networkModule = new Lazy(() => new Modules.Network.NetworkModule(_broker)); + _inputModule = new Lazy(() => new Modules.Input.InputModule(_broker)); + _scriptModule = new Lazy(() => new Modules.Script.ScriptModule(_broker)); + _logModule = new Lazy(() => new Modules.Log.LogModule(_broker)); + _storageModule = new Lazy(() => new Modules.Storage.StorageModule(_broker)); + } + + internal Modules.Session.SessionModule SessionModule => _sessionModule.Value; + internal Modules.BrowsingContext.BrowsingContextModule BrowsingContextModule => _browsingContextModule.Value; + public Modules.Browser.BrowserModule Browser => _browserModule.Value; + public Modules.Network.NetworkModule Network => _networkModule.Value; + internal Modules.Input.InputModule InputModule => _inputModule.Value; + internal Modules.Script.ScriptModule ScriptModule => _scriptModule.Value; + public Modules.Log.LogModule Log => _logModule.Value; + public Modules.Storage.StorageModule Storage => _storageModule.Value; + + public Task StatusAsync() + { + return SessionModule.StatusAsync(); + } + + public static async Task ConnectAsync(string url) + { + var bidi = new BiDi(url); + + await bidi._broker.ConnectAsync(default).ConfigureAwait(false); + + return bidi; + } + + public Task CreateBrowsingContextAsync(Modules.BrowsingContext.BrowsingContextType type, Modules.BrowsingContext.CreateOptions? options = null) + { + return BrowsingContextModule.CreateAsync(type, options); + } + + public Task> GetBrowsingContextTreeAsync(Modules.BrowsingContext.GetTreeOptions? options = null) + { + return BrowsingContextModule.GetTreeAsync(options); + } + + public Task EndAsync(Modules.Session.EndOptions? options = null) + { + return SessionModule.EndAsync(options); + } + + public async ValueTask DisposeAsync() + { + await _broker.DisposeAsync().ConfigureAwait(false); + + _transport?.Dispose(); + } + + public Task OnBrowsingContextCreatedAsync(Func handler, BrowsingContextsSubscriptionOptions? options = null) + { + return BrowsingContextModule.OnContextCreatedAsync(handler, options); + } + + public Task OnBrowsingContextCreatedAsync(Action handler, BrowsingContextsSubscriptionOptions? options = null) + { + return BrowsingContextModule.OnContextCreatedAsync(handler, options); + } + + public Task OnBrowsingContextDestroyedAsync(Func handler, BrowsingContextsSubscriptionOptions? options = null) + { + return BrowsingContextModule.OnContextDestroyedAsync(handler, options); + } + + public Task OnBrowsingContextDestroyedAsync(Action handler, BrowsingContextsSubscriptionOptions? options = null) + { + return BrowsingContextModule.OnContextDestroyedAsync(handler, options); + } + + public Task OnUserPromptOpenedAsync(Func handler, BrowsingContextsSubscriptionOptions? options = null) + { + return BrowsingContextModule.OnUserPromptOpenedAsync(handler, options); + } + + public Task OnUserPromptOpenedAsync(Action handler, BrowsingContextsSubscriptionOptions? options = null) + { + return BrowsingContextModule.OnUserPromptOpenedAsync(handler, options); + } + + public Task OnUserPromptClosedAsync(Func handler, BrowsingContextsSubscriptionOptions? options = null) + { + return BrowsingContextModule.OnUserPromptClosedAsync(handler, options); + } + + public Task OnUserPromptClosedAsync(Action handler, BrowsingContextsSubscriptionOptions? options = null) + { + return BrowsingContextModule.OnUserPromptClosedAsync(handler, options); + } +} diff --git a/dotnet/src/webdriver/BiDi/BiDiException.cs b/dotnet/src/webdriver/BiDi/BiDiException.cs new file mode 100644 index 0000000000000..774d727339758 --- /dev/null +++ b/dotnet/src/webdriver/BiDi/BiDiException.cs @@ -0,0 +1,10 @@ +using System; + +namespace OpenQA.Selenium.BiDi; + +public class BiDiException : Exception +{ + public BiDiException(string message) : base(message) + { + } +} diff --git a/dotnet/src/webdriver/BiDi/Communication/Broker.cs b/dotnet/src/webdriver/BiDi/Communication/Broker.cs new file mode 100644 index 0000000000000..101177b3a195c --- /dev/null +++ b/dotnet/src/webdriver/BiDi/Communication/Broker.cs @@ -0,0 +1,264 @@ +using OpenQA.Selenium.BiDi.Communication.Json.Converters; +using OpenQA.Selenium.BiDi.Communication.Transport; +using OpenQA.Selenium.Internal.Logging; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Text.Json; +using System.Text.Json.Serialization; +using System.Threading; +using System.Threading.Tasks; + +namespace OpenQA.Selenium.BiDi.Communication; + +public class Broker : IAsyncDisposable +{ + private readonly ILogger _logger = Log.GetLogger(); + + private readonly BiDi _bidi; + private readonly ITransport _transport; + + private readonly ConcurrentDictionary> _pendingCommands = new(); + private readonly BlockingCollection _pendingEvents = []; + + private readonly ConcurrentDictionary> _eventHandlers = new(); + + private int _currentCommandId; + + private static readonly TaskFactory _myTaskFactory = new(CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskContinuationOptions.None, TaskScheduler.Default); + + private Task? _receivingMessageTask; + private Task? _eventEmitterTask; + private CancellationTokenSource? _receiveMessagesCancellationTokenSource; + + private readonly JsonSerializerOptions _jsonSerializerOptions; + + public Broker(BiDi bidi, ITransport transport) + { + _bidi = bidi; + _transport = transport; + + _jsonSerializerOptions = new JsonSerializerOptions + { + PropertyNameCaseInsensitive = true, + PropertyNamingPolicy = JsonNamingPolicy.CamelCase, + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, + Converters = + { + new BrowsingContextConverter(_bidi), + new BrowserUserContextConverter(bidi), + new NavigationConverter(), + new InterceptConverter(_bidi), + new RequestConverter(_bidi), + new ChannelConverter(_bidi), + new HandleConverter(_bidi), + new InternalIdConverter(_bidi), + new PreloadScriptConverter(_bidi), + new RealmConverter(_bidi), + new RealmTypeConverter(), + new DateTimeOffsetConverter(), + new JsonStringEnumConverter(JsonNamingPolicy.CamelCase), + + // https://github.com/dotnet/runtime/issues/72604 + new Json.Converters.Polymorphic.MessageConverter(), + new Json.Converters.Polymorphic.EvaluateResultConverter(), + new Json.Converters.Polymorphic.RemoteValueConverter(), + new Json.Converters.Polymorphic.RealmInfoConverter(), + new Json.Converters.Polymorphic.LogEntryConverter(), + // + } + }; + } + + public async Task ConnectAsync(CancellationToken cancellationToken) + { + await _transport.ConnectAsync(cancellationToken).ConfigureAwait(false); + + _receiveMessagesCancellationTokenSource = new CancellationTokenSource(); + _receivingMessageTask = _myTaskFactory.StartNew(async () => await ReceiveMessagesAsync(_receiveMessagesCancellationTokenSource.Token), TaskCreationOptions.LongRunning).Unwrap(); + _eventEmitterTask = _myTaskFactory.StartNew(async () => await ProcessEventsAwaiterAsync(), TaskCreationOptions.LongRunning).Unwrap(); + } + + private async Task ReceiveMessagesAsync(CancellationToken cancellationToken) + { + while (!cancellationToken.IsCancellationRequested) + { + var message = await _transport.ReceiveAsJsonAsync(_jsonSerializerOptions, cancellationToken); + + switch (message) + { + case MessageSuccess messageSuccess: + _pendingCommands[messageSuccess.Id].SetResult(messageSuccess.Result); + _pendingCommands.TryRemove(messageSuccess.Id, out _); + break; + case MessageEvent messageEvent: + _pendingEvents.Add(messageEvent); + break; + case MessageError mesageError: + _pendingCommands[mesageError.Id].SetException(new BiDiException($"{mesageError.Error}: {mesageError.Message}")); + _pendingCommands.TryRemove(mesageError.Id, out _); + break; + } + } + } + + private async Task ProcessEventsAwaiterAsync() + { + foreach (var result in _pendingEvents.GetConsumingEnumerable()) + { + try + { + if (_eventHandlers.TryGetValue(result.Method, out var eventHandlers)) + { + if (eventHandlers is not null) + { + foreach (var handler in eventHandlers.ToArray()) // copy handlers avoiding modified collection while iterating + { + var args = (EventArgs)result.Params.Deserialize(handler.EventArgsType, _jsonSerializerOptions)!; + + args.BiDi = _bidi; + + // handle browsing context subscriber + if (handler.Contexts is not null && args is BrowsingContextEventArgs browsingContextEventArgs && handler.Contexts.Contains(browsingContextEventArgs.Context)) + { + await handler.InvokeAsync(args).ConfigureAwait(false); + } + // handle only session subscriber + else if (handler.Contexts is null) + { + await handler.InvokeAsync(args).ConfigureAwait(false); + } + } + } + } + } + catch (Exception ex) + { + if (_logger.IsEnabled(LogEventLevel.Error)) + { + _logger.Error($"Unhandled error processing BiDi event: {ex}"); + } + } + } + } + + public async Task ExecuteCommandAsync(Command command, CommandOptions? options) + { + var result = await ExecuteCommandCoreAsync(command, options).ConfigureAwait(false); + + return (TResult)((JsonElement)result).Deserialize(typeof(TResult), _jsonSerializerOptions)!; + } + + public async Task ExecuteCommandAsync(Command command, CommandOptions? options) + { + await ExecuteCommandCoreAsync(command, options).ConfigureAwait(false); + } + + private async Task ExecuteCommandCoreAsync(Command command, CommandOptions? options) + { + command.Id = Interlocked.Increment(ref _currentCommandId); + + var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + + var timeout = options?.Timeout ?? TimeSpan.FromSeconds(30); + + using var cts = new CancellationTokenSource(timeout); + + cts.Token.Register(() => tcs.TrySetCanceled(cts.Token)); + + _pendingCommands[command.Id] = tcs; + + await _transport.SendAsJsonAsync(command, _jsonSerializerOptions, cts.Token).ConfigureAwait(false); + + return await tcs.Task.ConfigureAwait(false); + } + + public async Task SubscribeAsync(string eventName, Action action, SubscriptionOptions? options = null) + where TEventArgs : EventArgs + { + var handlers = _eventHandlers.GetOrAdd(eventName, (a) => []); + + if (options is BrowsingContextsSubscriptionOptions browsingContextsOptions) + { + await _bidi.SessionModule.SubscribeAsync([eventName], new() { Contexts = browsingContextsOptions.Contexts }).ConfigureAwait(false); + + var eventHandler = new SyncEventHandler(eventName, action, browsingContextsOptions?.Contexts); + + handlers.Add(eventHandler); + + return new Subscription(this, eventHandler); + } + else + { + await _bidi.SessionModule.SubscribeAsync([eventName]).ConfigureAwait(false); + + var eventHandler = new SyncEventHandler(eventName, action); + + handlers.Add(eventHandler); + + return new Subscription(this, eventHandler); + } + } + + public async Task SubscribeAsync(string eventName, Func func, SubscriptionOptions? options = null) + where TEventArgs : EventArgs + { + var handlers = _eventHandlers.GetOrAdd(eventName, (a) => []); + + if (options is BrowsingContextsSubscriptionOptions browsingContextsOptions) + { + await _bidi.SessionModule.SubscribeAsync([eventName], new() { Contexts = browsingContextsOptions.Contexts }).ConfigureAwait(false); + + var eventHandler = new AsyncEventHandler(eventName, func, browsingContextsOptions.Contexts); + + handlers.Add(eventHandler); + + return new Subscription(this, eventHandler); + } + else + { + await _bidi.SessionModule.SubscribeAsync([eventName]).ConfigureAwait(false); + + var eventHandler = new AsyncEventHandler(eventName, func); + + handlers.Add(eventHandler); + + return new Subscription(this, eventHandler); + } + } + + public async Task UnsubscribeAsync(EventHandler eventHandler) + { + var eventHandlers = _eventHandlers[eventHandler.EventName]; + + eventHandlers.Remove(eventHandler); + + if (eventHandler.Contexts is not null) + { + if (!eventHandlers.Any(h => eventHandler.Contexts.Equals(h.Contexts)) && !eventHandlers.Any(h => h.Contexts is null)) + { + await _bidi.SessionModule.UnsubscribeAsync([eventHandler.EventName], new() { Contexts = eventHandler.Contexts }).ConfigureAwait(false); + } + } + else + { + if (!eventHandlers.Any(h => h.Contexts is not null) && !eventHandlers.Any(h => h.Contexts is null)) + { + await _bidi.SessionModule.UnsubscribeAsync([eventHandler.EventName]).ConfigureAwait(false); + } + } + } + + public async ValueTask DisposeAsync() + { + _pendingEvents.CompleteAdding(); + + _receiveMessagesCancellationTokenSource?.Cancel(); + + if (_eventEmitterTask is not null) + { + await _eventEmitterTask.ConfigureAwait(false); + } + } +} diff --git a/dotnet/src/webdriver/BiDi/Communication/Command.cs b/dotnet/src/webdriver/BiDi/Communication/Command.cs new file mode 100644 index 0000000000000..6b556b0e97ea0 --- /dev/null +++ b/dotnet/src/webdriver/BiDi/Communication/Command.cs @@ -0,0 +1,67 @@ +using System.Text.Json.Serialization; + +namespace OpenQA.Selenium.BiDi.Communication; + +[JsonPolymorphic(TypeDiscriminatorPropertyName = "method")] + +[JsonDerivedType(typeof(Modules.Session.StatusCommand), "session.status")] +[JsonDerivedType(typeof(Modules.Session.SubscribeCommand), "session.subscribe")] +[JsonDerivedType(typeof(Modules.Session.UnsubscribeCommand), "session.unsubscribe")] +[JsonDerivedType(typeof(Modules.Session.NewCommand), "session.new")] +[JsonDerivedType(typeof(Modules.Session.EndCommand), "session.end")] + +[JsonDerivedType(typeof(Modules.Browser.CreateUserContextCommand), "browser.createUserContext")] +[JsonDerivedType(typeof(Modules.Browser.GetUserContextsCommand), "browser.getUserContexts")] +[JsonDerivedType(typeof(Modules.Browser.RemoveUserContextCommand), "browser.removeUserContext")] +[JsonDerivedType(typeof(Modules.Browser.CloseCommand), "browser.close")] + +[JsonDerivedType(typeof(Modules.BrowsingContext.CreateCommand), "browsingContext.create")] +[JsonDerivedType(typeof(Modules.BrowsingContext.NavigateCommand), "browsingContext.navigate")] +[JsonDerivedType(typeof(Modules.BrowsingContext.ReloadCommand), "browsingContext.reload")] +[JsonDerivedType(typeof(Modules.BrowsingContext.TraverseHistoryCommand), "browsingContext.traverseHistory")] +[JsonDerivedType(typeof(Modules.BrowsingContext.LocateNodesCommand), "browsingContext.locateNodes")] +[JsonDerivedType(typeof(Modules.BrowsingContext.ActivateCommand), "browsingContext.activate")] +[JsonDerivedType(typeof(Modules.BrowsingContext.CaptureScreenshotCommand), "browsingContext.captureScreenshot")] +[JsonDerivedType(typeof(Modules.BrowsingContext.SetViewportCommand), "browsingContext.setViewport")] +[JsonDerivedType(typeof(Modules.BrowsingContext.GetTreeCommand), "browsingContext.getTree")] +[JsonDerivedType(typeof(Modules.BrowsingContext.PrintCommand), "browsingContext.print")] +[JsonDerivedType(typeof(Modules.BrowsingContext.HandleUserPromptCommand), "browsingContext.handleUserPrompt")] +[JsonDerivedType(typeof(Modules.BrowsingContext.CloseCommand), "browsingContext.close")] + +[JsonDerivedType(typeof(Modules.Network.AddInterceptCommand), "network.addIntercept")] +[JsonDerivedType(typeof(Modules.Network.ContinueRequestCommand), "network.continueRequest")] +[JsonDerivedType(typeof(Modules.Network.ContinueResponseCommand), "network.continueResponse")] +[JsonDerivedType(typeof(Modules.Network.FailRequestCommand), "network.failRequest")] +[JsonDerivedType(typeof(Modules.Network.ProvideResponseCommand), "network.provideResponse")] +[JsonDerivedType(typeof(Modules.Network.ContinueWithAuthCommand), "network.continueWithAuth")] +[JsonDerivedType(typeof(Modules.Network.RemoveInterceptCommand), "network.removeIntercept")] + +[JsonDerivedType(typeof(Modules.Script.AddPreloadScriptCommand), "script.addPreloadScript")] +[JsonDerivedType(typeof(Modules.Script.RemovePreloadScriptCommand), "script.removePreloadScript")] +[JsonDerivedType(typeof(Modules.Script.EvaluateCommand), "script.evaluate")] +[JsonDerivedType(typeof(Modules.Script.CallFunctionCommand), "script.callFunction")] +[JsonDerivedType(typeof(Modules.Script.DisownCommand), "script.disown")] +[JsonDerivedType(typeof(Modules.Script.GetRealmsCommand), "script.getRealms")] + +[JsonDerivedType(typeof(Modules.Input.PerformActionsCommand), "input.performActions")] +[JsonDerivedType(typeof(Modules.Input.ReleaseActionsCommand), "input.releaseActions")] + +[JsonDerivedType(typeof(Modules.Storage.GetCookiesCommand), "storage.getCookies")] +[JsonDerivedType(typeof(Modules.Storage.DeleteCookiesCommand), "storage.deleteCookies")] +[JsonDerivedType(typeof(Modules.Storage.SetCookieCommand), "storage.setCookie")] + +public abstract class Command +{ + public int Id { get; internal set; } +} + +internal abstract class Command(TCommandParameters @params) : Command + where TCommandParameters : CommandParameters +{ + public TCommandParameters Params { get; } = @params; +} + +internal record CommandParameters +{ + public static CommandParameters Empty { get; } = new CommandParameters(); +} diff --git a/dotnet/src/webdriver/BiDi/Communication/CommandOptions.cs b/dotnet/src/webdriver/BiDi/Communication/CommandOptions.cs new file mode 100644 index 0000000000000..7ee0c388d391f --- /dev/null +++ b/dotnet/src/webdriver/BiDi/Communication/CommandOptions.cs @@ -0,0 +1,8 @@ +using System; + +namespace OpenQA.Selenium.BiDi.Communication; + +public record CommandOptions +{ + public TimeSpan? Timeout { get; set; } +} diff --git a/dotnet/src/webdriver/BiDi/Communication/EventHandler.cs b/dotnet/src/webdriver/BiDi/Communication/EventHandler.cs new file mode 100644 index 0000000000000..7e939b395ccae --- /dev/null +++ b/dotnet/src/webdriver/BiDi/Communication/EventHandler.cs @@ -0,0 +1,39 @@ +using OpenQA.Selenium.BiDi.Modules.BrowsingContext; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace OpenQA.Selenium.BiDi.Communication; + +public abstract class EventHandler(string eventName, Type eventArgsType, IEnumerable? contexts = null) +{ + public string EventName { get; } = eventName; + public Type EventArgsType { get; set; } = eventArgsType; + public IEnumerable? Contexts { get; } = contexts; + + public abstract ValueTask InvokeAsync(object args); +} + +internal class AsyncEventHandler(string eventName, Func func, IEnumerable? contexts = null) + : EventHandler(eventName, typeof(TEventArgs), contexts) where TEventArgs : EventArgs +{ + private readonly Func _func = func; + + public override async ValueTask InvokeAsync(object args) + { + await _func((TEventArgs)args).ConfigureAwait(false); + } +} + +internal class SyncEventHandler(string eventName, Action action, IEnumerable? contexts = null) + : EventHandler(eventName, typeof(TEventArgs), contexts) where TEventArgs : EventArgs +{ + private readonly Action _action = action; + + public override ValueTask InvokeAsync(object args) + { + _action((TEventArgs)args); + + return default; + } +} diff --git a/dotnet/src/webdriver/BiDi/Communication/Json/Converters/BrowserUserContextConverter.cs b/dotnet/src/webdriver/BiDi/Communication/Json/Converters/BrowserUserContextConverter.cs new file mode 100644 index 0000000000000..af6134f21c5b6 --- /dev/null +++ b/dotnet/src/webdriver/BiDi/Communication/Json/Converters/BrowserUserContextConverter.cs @@ -0,0 +1,28 @@ +using OpenQA.Selenium.BiDi.Modules.Browser; +using System; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace OpenQA.Selenium.BiDi.Communication.Json.Converters; + +internal class BrowserUserContextConverter : JsonConverter +{ + private readonly BiDi _bidi; + + public BrowserUserContextConverter(BiDi bidi) + { + _bidi = bidi; + } + + public override UserContext? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + var id = reader.GetString(); + + return new UserContext(_bidi, id!); + } + + public override void Write(Utf8JsonWriter writer, UserContext value, JsonSerializerOptions options) + { + writer.WriteStringValue(value.Id); + } +} diff --git a/dotnet/src/webdriver/BiDi/Communication/Json/Converters/BrowsingContextConverter.cs b/dotnet/src/webdriver/BiDi/Communication/Json/Converters/BrowsingContextConverter.cs new file mode 100644 index 0000000000000..f927ac07fc243 --- /dev/null +++ b/dotnet/src/webdriver/BiDi/Communication/Json/Converters/BrowsingContextConverter.cs @@ -0,0 +1,28 @@ +using OpenQA.Selenium.BiDi.Modules.BrowsingContext; +using System; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace OpenQA.Selenium.BiDi.Communication.Json.Converters; + +internal class BrowsingContextConverter : JsonConverter +{ + private readonly BiDi _bidi; + + public BrowsingContextConverter(BiDi bidi) + { + _bidi = bidi; + } + + public override BrowsingContext? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + var id = reader.GetString(); + + return new BrowsingContext(_bidi, id!); + } + + public override void Write(Utf8JsonWriter writer, BrowsingContext value, JsonSerializerOptions options) + { + writer.WriteStringValue(value.Id); + } +} diff --git a/dotnet/src/webdriver/BiDi/Communication/Json/Converters/ChannelConverter.cs b/dotnet/src/webdriver/BiDi/Communication/Json/Converters/ChannelConverter.cs new file mode 100644 index 0000000000000..9bdf7ece30765 --- /dev/null +++ b/dotnet/src/webdriver/BiDi/Communication/Json/Converters/ChannelConverter.cs @@ -0,0 +1,28 @@ +using OpenQA.Selenium.BiDi.Modules.Script; +using System; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace OpenQA.Selenium.BiDi.Communication.Json.Converters; + +internal class ChannelConverter : JsonConverter +{ + private readonly BiDi _bidi; + + public ChannelConverter(BiDi bidi) + { + _bidi = bidi; + } + + public override Channel? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + var id = reader.GetString(); + + return new Channel(_bidi, id!); + } + + public override void Write(Utf8JsonWriter writer, Channel value, JsonSerializerOptions options) + { + writer.WriteStringValue(value.Id); + } +} diff --git a/dotnet/src/webdriver/BiDi/Communication/Json/Converters/DateTimeOffsetConverter.cs b/dotnet/src/webdriver/BiDi/Communication/Json/Converters/DateTimeOffsetConverter.cs new file mode 100644 index 0000000000000..be0f2d64720b6 --- /dev/null +++ b/dotnet/src/webdriver/BiDi/Communication/Json/Converters/DateTimeOffsetConverter.cs @@ -0,0 +1,27 @@ +using System; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace OpenQA.Selenium.BiDi.Communication.Json.Converters; + +internal class DateTimeOffsetConverter : JsonConverter +{ + public override DateTimeOffset Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + // Workaround: it should be Int64, chrome uses double for `expiry` like "expiry":1737379944.308351 + + if (reader.TryGetInt64(out long unixTime) is false) + { + var doubleValue = reader.GetDouble(); + + unixTime = Convert.ToInt64(doubleValue); + } + + return DateTimeOffset.FromUnixTimeMilliseconds(unixTime); + } + + public override void Write(Utf8JsonWriter writer, DateTimeOffset value, JsonSerializerOptions options) + { + writer.WriteNumberValue(value.ToUnixTimeMilliseconds()); + } +} diff --git a/dotnet/src/webdriver/BiDi/Communication/Json/Converters/HandleConverter.cs b/dotnet/src/webdriver/BiDi/Communication/Json/Converters/HandleConverter.cs new file mode 100644 index 0000000000000..e31a2e9a74e4a --- /dev/null +++ b/dotnet/src/webdriver/BiDi/Communication/Json/Converters/HandleConverter.cs @@ -0,0 +1,28 @@ +using OpenQA.Selenium.BiDi.Modules.Script; +using System; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace OpenQA.Selenium.BiDi.Communication.Json.Converters; + +internal class HandleConverter : JsonConverter +{ + private readonly BiDi _bidi; + + public HandleConverter(BiDi bidi) + { + _bidi = bidi; + } + + public override Handle? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + var id = reader.GetString(); + + return new Handle(_bidi, id!); + } + + public override void Write(Utf8JsonWriter writer, Handle value, JsonSerializerOptions options) + { + writer.WriteStringValue(value.Id); + } +} diff --git a/dotnet/src/webdriver/BiDi/Communication/Json/Converters/InterceptConverter.cs b/dotnet/src/webdriver/BiDi/Communication/Json/Converters/InterceptConverter.cs new file mode 100644 index 0000000000000..2b499068e16df --- /dev/null +++ b/dotnet/src/webdriver/BiDi/Communication/Json/Converters/InterceptConverter.cs @@ -0,0 +1,28 @@ +using OpenQA.Selenium.BiDi.Modules.Network; +using System; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace OpenQA.Selenium.BiDi.Communication.Json.Converters; + +internal class InterceptConverter : JsonConverter +{ + private readonly BiDi _bidi; + + public InterceptConverter(BiDi bidi) + { + _bidi = bidi; + } + + public override Intercept? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + var id = reader.GetString(); + + return new Intercept(_bidi, id!); + } + + public override void Write(Utf8JsonWriter writer, Intercept value, JsonSerializerOptions options) + { + writer.WriteStringValue(value.Id); + } +} diff --git a/dotnet/src/webdriver/BiDi/Communication/Json/Converters/InternalIdConverter.cs b/dotnet/src/webdriver/BiDi/Communication/Json/Converters/InternalIdConverter.cs new file mode 100644 index 0000000000000..ee26309cf0659 --- /dev/null +++ b/dotnet/src/webdriver/BiDi/Communication/Json/Converters/InternalIdConverter.cs @@ -0,0 +1,28 @@ +using OpenQA.Selenium.BiDi.Modules.Script; +using System; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace OpenQA.Selenium.BiDi.Communication.Json.Converters; + +internal class InternalIdConverter : JsonConverter +{ + private readonly BiDi _bidi; + + public InternalIdConverter(BiDi bidi) + { + _bidi = bidi; + } + + public override InternalId? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + var id = reader.GetString(); + + return new InternalId(_bidi, id!); + } + + public override void Write(Utf8JsonWriter writer, InternalId value, JsonSerializerOptions options) + { + writer.WriteStringValue(value.Id); + } +} diff --git a/dotnet/src/webdriver/BiDi/Communication/Json/Converters/NavigationConverter.cs b/dotnet/src/webdriver/BiDi/Communication/Json/Converters/NavigationConverter.cs new file mode 100644 index 0000000000000..7ff71eeddca69 --- /dev/null +++ b/dotnet/src/webdriver/BiDi/Communication/Json/Converters/NavigationConverter.cs @@ -0,0 +1,21 @@ +using OpenQA.Selenium.BiDi.Modules.BrowsingContext; +using System; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace OpenQA.Selenium.BiDi.Communication.Json.Converters; + +internal class NavigationConverter : JsonConverter +{ + public override Navigation? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + var id = reader.GetString(); + + return new Navigation(id!); + } + + public override void Write(Utf8JsonWriter writer, Navigation value, JsonSerializerOptions options) + { + writer.WriteStringValue(value.Id); + } +} diff --git a/dotnet/src/webdriver/BiDi/Communication/Json/Converters/Polymorphic/EvaluateResultConverter.cs b/dotnet/src/webdriver/BiDi/Communication/Json/Converters/Polymorphic/EvaluateResultConverter.cs new file mode 100644 index 0000000000000..7c58a20b51a72 --- /dev/null +++ b/dotnet/src/webdriver/BiDi/Communication/Json/Converters/Polymorphic/EvaluateResultConverter.cs @@ -0,0 +1,27 @@ +using OpenQA.Selenium.BiDi.Modules.Script; +using System; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace OpenQA.Selenium.BiDi.Communication.Json.Converters.Polymorphic; + +// https://github.com/dotnet/runtime/issues/72604 +internal class EvaluateResultConverter : JsonConverter +{ + public override EvaluateResult? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + var jsonDocument = JsonDocument.ParseValue(ref reader); + + return jsonDocument.RootElement.GetProperty("type").ToString() switch + { + "success" => jsonDocument.Deserialize(options), + "exception" => jsonDocument.Deserialize(options), + _ => null, + }; + } + + public override void Write(Utf8JsonWriter writer, EvaluateResult value, JsonSerializerOptions options) + { + throw new NotImplementedException(); + } +} diff --git a/dotnet/src/webdriver/BiDi/Communication/Json/Converters/Polymorphic/LogEntryConverter.cs b/dotnet/src/webdriver/BiDi/Communication/Json/Converters/Polymorphic/LogEntryConverter.cs new file mode 100644 index 0000000000000..4698b8f425910 --- /dev/null +++ b/dotnet/src/webdriver/BiDi/Communication/Json/Converters/Polymorphic/LogEntryConverter.cs @@ -0,0 +1,27 @@ +using OpenQA.Selenium.BiDi.Modules.Log; +using System; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace OpenQA.Selenium.BiDi.Communication.Json.Converters.Polymorphic; + +// https://github.com/dotnet/runtime/issues/72604 +internal class LogEntryConverter : JsonConverter +{ + public override BaseLogEntry? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + var jsonDocument = JsonDocument.ParseValue(ref reader); + + return jsonDocument.RootElement.GetProperty("type").ToString() switch + { + "console" => jsonDocument.Deserialize(options), + "javascript" => jsonDocument.Deserialize(options), + _ => null, + }; + } + + public override void Write(Utf8JsonWriter writer, BaseLogEntry value, JsonSerializerOptions options) + { + throw new NotImplementedException(); + } +} diff --git a/dotnet/src/webdriver/BiDi/Communication/Json/Converters/Polymorphic/MessageConverter.cs b/dotnet/src/webdriver/BiDi/Communication/Json/Converters/Polymorphic/MessageConverter.cs new file mode 100644 index 0000000000000..34e313148b269 --- /dev/null +++ b/dotnet/src/webdriver/BiDi/Communication/Json/Converters/Polymorphic/MessageConverter.cs @@ -0,0 +1,27 @@ +using System; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace OpenQA.Selenium.BiDi.Communication.Json.Converters.Polymorphic; + +// https://github.com/dotnet/runtime/issues/72604 +internal class MessageConverter : JsonConverter +{ + public override Message? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + var jsonDocument = JsonDocument.ParseValue(ref reader); + + return jsonDocument.RootElement.GetProperty("type").ToString() switch + { + "success" => jsonDocument.Deserialize(options), + "error" => jsonDocument.Deserialize(options), + "event" => jsonDocument.Deserialize(options), + _ => null, + }; + } + + public override void Write(Utf8JsonWriter writer, Message value, JsonSerializerOptions options) + { + throw new NotImplementedException(); + } +} diff --git a/dotnet/src/webdriver/BiDi/Communication/Json/Converters/Polymorphic/RealmInfoConverter.cs b/dotnet/src/webdriver/BiDi/Communication/Json/Converters/Polymorphic/RealmInfoConverter.cs new file mode 100644 index 0000000000000..af3e4ae11cca8 --- /dev/null +++ b/dotnet/src/webdriver/BiDi/Communication/Json/Converters/Polymorphic/RealmInfoConverter.cs @@ -0,0 +1,33 @@ +using OpenQA.Selenium.BiDi.Modules.Script; +using System; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace OpenQA.Selenium.BiDi.Communication.Json.Converters.Polymorphic; + +// https://github.com/dotnet/runtime/issues/72604 +internal class RealmInfoConverter : JsonConverter +{ + public override RealmInfo? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + var jsonDocument = JsonDocument.ParseValue(ref reader); + + return jsonDocument.RootElement.GetProperty("type").ToString() switch + { + "window" => jsonDocument.Deserialize(options), + "dedicated-worker" => jsonDocument.Deserialize(options), + "shared-worker" => jsonDocument.Deserialize(options), + "service-worker" => jsonDocument.Deserialize(options), + "worker" => jsonDocument.Deserialize(options), + "paint-worklet" => jsonDocument.Deserialize(options), + "audio-worklet" => jsonDocument.Deserialize(options), + "worklet" => jsonDocument.Deserialize(options), + _ => null, + }; + } + + public override void Write(Utf8JsonWriter writer, RealmInfo value, JsonSerializerOptions options) + { + throw new NotImplementedException(); + } +} diff --git a/dotnet/src/webdriver/BiDi/Communication/Json/Converters/Polymorphic/RemoteValueConverter.cs b/dotnet/src/webdriver/BiDi/Communication/Json/Converters/Polymorphic/RemoteValueConverter.cs new file mode 100644 index 0000000000000..0ad66fdc0c79a --- /dev/null +++ b/dotnet/src/webdriver/BiDi/Communication/Json/Converters/Polymorphic/RemoteValueConverter.cs @@ -0,0 +1,48 @@ +using OpenQA.Selenium.BiDi.Modules.Script; +using System; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace OpenQA.Selenium.BiDi.Communication.Json.Converters.Polymorphic; + +// https://github.com/dotnet/runtime/issues/72604 +internal class RemoteValueConverter : JsonConverter +{ + public override RemoteValue? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + var jsonDocument = JsonDocument.ParseValue(ref reader); + + return jsonDocument.RootElement.GetProperty("type").ToString() switch + { + "number" => jsonDocument.Deserialize(options), + "string" => jsonDocument.Deserialize(options), + "null" => jsonDocument.Deserialize(options), + "undefined" => jsonDocument.Deserialize(options), + "symbol" => jsonDocument.Deserialize(options), + "object" => jsonDocument.Deserialize(options), + "function" => jsonDocument.Deserialize(options), + "regexp" => jsonDocument.Deserialize(options), + "date" => jsonDocument.Deserialize(options), + "map" => jsonDocument.Deserialize(options), + "set" => jsonDocument.Deserialize(options), + "weakmap" => jsonDocument.Deserialize(options), + "weakset" => jsonDocument.Deserialize(options), + "generator" => jsonDocument.Deserialize(options), + "error" => jsonDocument.Deserialize(options), + "proxy" => jsonDocument.Deserialize(options), + "promise" => jsonDocument.Deserialize(options), + "typedarray" => jsonDocument.Deserialize(options), + "arraybuffer" => jsonDocument.Deserialize(options), + "nodelist" => jsonDocument.Deserialize(options), + "htmlcollection" => jsonDocument.Deserialize(options), + "node" => jsonDocument.Deserialize(options), + "window" => jsonDocument.Deserialize(options), + _ => null, + }; + } + + public override void Write(Utf8JsonWriter writer, RemoteValue value, JsonSerializerOptions options) + { + throw new NotImplementedException(); + } +} diff --git a/dotnet/src/webdriver/BiDi/Communication/Json/Converters/PreloadScriptConverter.cs b/dotnet/src/webdriver/BiDi/Communication/Json/Converters/PreloadScriptConverter.cs new file mode 100644 index 0000000000000..da0657baeed98 --- /dev/null +++ b/dotnet/src/webdriver/BiDi/Communication/Json/Converters/PreloadScriptConverter.cs @@ -0,0 +1,28 @@ +using OpenQA.Selenium.BiDi.Modules.Script; +using System; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace OpenQA.Selenium.BiDi.Communication.Json.Converters; + +internal class PreloadScriptConverter : JsonConverter +{ + private readonly BiDi _bidi; + + public PreloadScriptConverter(BiDi bidi) + { + _bidi = bidi; + } + + public override PreloadScript? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + var id = reader.GetString(); + + return new PreloadScript(_bidi, id!); + } + + public override void Write(Utf8JsonWriter writer, PreloadScript value, JsonSerializerOptions options) + { + writer.WriteStringValue(value.Id); + } +} diff --git a/dotnet/src/webdriver/BiDi/Communication/Json/Converters/RealmConverter.cs b/dotnet/src/webdriver/BiDi/Communication/Json/Converters/RealmConverter.cs new file mode 100644 index 0000000000000..dfa32ca44c3bc --- /dev/null +++ b/dotnet/src/webdriver/BiDi/Communication/Json/Converters/RealmConverter.cs @@ -0,0 +1,28 @@ +using OpenQA.Selenium.BiDi.Modules.Script; +using System; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace OpenQA.Selenium.BiDi.Communication.Json.Converters; + +internal class RealmConverter : JsonConverter +{ + private readonly BiDi _bidi; + + public RealmConverter(BiDi bidi) + { + _bidi = bidi; + } + + public override Realm? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + var id = reader.GetString(); + + return new Realm(_bidi, id!); + } + + public override void Write(Utf8JsonWriter writer, Realm value, JsonSerializerOptions options) + { + writer.WriteStringValue(value.Id); + } +} diff --git a/dotnet/src/webdriver/BiDi/Communication/Json/Converters/RealmTypeConverter.cs b/dotnet/src/webdriver/BiDi/Communication/Json/Converters/RealmTypeConverter.cs new file mode 100644 index 0000000000000..619b6da77a9c1 --- /dev/null +++ b/dotnet/src/webdriver/BiDi/Communication/Json/Converters/RealmTypeConverter.cs @@ -0,0 +1,32 @@ +using OpenQA.Selenium.BiDi.Modules.Script; +using System; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace OpenQA.Selenium.BiDi.Communication.Json.Converters; + +internal class RealmTypeConverter : JsonConverter +{ + public override RealmType Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + var realmType = reader.GetString(); + + return realmType switch + { + "window" => RealmType.Window, + "dedicated-worker" => RealmType.DedicatedWorker, + "shared-worker" => RealmType.SharedWorker, + "service-worker" => RealmType.ServiceWorker, + "worker" => RealmType.Worker, + "paint-worker" => RealmType.PaintWorker, + "audio-worker" => RealmType.AudioWorker, + "worklet" => RealmType.Worklet, + _ => throw new JsonException($"Unrecognized '{realmType}' value of {typeof(RealmType)}."), + }; + } + + public override void Write(Utf8JsonWriter writer, RealmType value, JsonSerializerOptions options) + { + throw new NotImplementedException(); + } +} diff --git a/dotnet/src/webdriver/BiDi/Communication/Json/Converters/RequestConverter.cs b/dotnet/src/webdriver/BiDi/Communication/Json/Converters/RequestConverter.cs new file mode 100644 index 0000000000000..83eece8c72aed --- /dev/null +++ b/dotnet/src/webdriver/BiDi/Communication/Json/Converters/RequestConverter.cs @@ -0,0 +1,28 @@ +using OpenQA.Selenium.BiDi.Modules.Network; +using System; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace OpenQA.Selenium.BiDi.Communication.Json.Converters; + +internal class RequestConverter : JsonConverter +{ + private readonly BiDi _bidi; + + public RequestConverter(BiDi bidi) + { + _bidi = bidi; + } + + public override Request? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + var id = reader.GetString(); + + return new Request(_bidi, id!); + } + + public override void Write(Utf8JsonWriter writer, Request value, JsonSerializerOptions options) + { + writer.WriteStringValue(value.Id); + } +} diff --git a/dotnet/src/webdriver/BiDi/Communication/Message.cs b/dotnet/src/webdriver/BiDi/Communication/Message.cs new file mode 100644 index 0000000000000..6be07a66b2b16 --- /dev/null +++ b/dotnet/src/webdriver/BiDi/Communication/Message.cs @@ -0,0 +1,21 @@ +using System.Text.Json; + +namespace OpenQA.Selenium.BiDi.Communication; + +// https://github.com/dotnet/runtime/issues/72604 +//[JsonPolymorphic(TypeDiscriminatorPropertyName = "type")] +//[JsonDerivedType(typeof(MessageSuccess), "success")] +//[JsonDerivedType(typeof(MessageError), "error")] +//[JsonDerivedType(typeof(MessageEvent), "event")] +internal abstract record Message; + +internal record MessageSuccess(int Id, JsonElement Result) : Message; + +internal record MessageError(int Id) : Message +{ + public string? Error { get; set; } + + public string? Message { get; set; } +} + +internal record MessageEvent(string Method, JsonElement Params) : Message; diff --git a/dotnet/src/webdriver/BiDi/Communication/Transport/ITransport.cs b/dotnet/src/webdriver/BiDi/Communication/Transport/ITransport.cs new file mode 100644 index 0000000000000..8e0d16be7c822 --- /dev/null +++ b/dotnet/src/webdriver/BiDi/Communication/Transport/ITransport.cs @@ -0,0 +1,15 @@ +using System.Text.Json; +using System.Threading.Tasks; +using System.Threading; +using System; + +namespace OpenQA.Selenium.BiDi.Communication.Transport; + +public interface ITransport : IDisposable +{ + Task ConnectAsync(CancellationToken cancellationToken); + + Task ReceiveAsJsonAsync(JsonSerializerOptions jsonSerializerOptions, CancellationToken cancellationToken); + + Task SendAsJsonAsync(Command command, JsonSerializerOptions jsonSerializerOptions, CancellationToken cancellationToken); +} diff --git a/dotnet/src/webdriver/BiDi/Communication/Transport/WebSocketTransport.cs b/dotnet/src/webdriver/BiDi/Communication/Transport/WebSocketTransport.cs new file mode 100644 index 0000000000000..a703d86943729 --- /dev/null +++ b/dotnet/src/webdriver/BiDi/Communication/Transport/WebSocketTransport.cs @@ -0,0 +1,66 @@ +using System; +using System.IO; +using System.Net.WebSockets; +using System.Threading.Tasks; +using System.Threading; +using System.Text.Json; +using System.Text; +using OpenQA.Selenium.Internal.Logging; + +namespace OpenQA.Selenium.BiDi.Communication.Transport; + +public class WebSocketTransport(Uri _uri) : ITransport, IDisposable +{ + private readonly static ILogger _logger = Log.GetLogger(); + + private readonly ClientWebSocket _webSocket = new(); + private readonly ArraySegment _receiveBuffer = new(new byte[1024 * 8]); + + public async Task ConnectAsync(CancellationToken cancellationToken) + { + _webSocket.Options.SetBuffer(_receiveBuffer.Count, _receiveBuffer.Count, _receiveBuffer); + await _webSocket.ConnectAsync(_uri, cancellationToken).ConfigureAwait(false); + } + + public async Task ReceiveAsJsonAsync(JsonSerializerOptions jsonSerializerOptions, CancellationToken cancellationToken) + { + using var ms = new MemoryStream(); + + WebSocketReceiveResult result; + + do + { + result = await _webSocket.ReceiveAsync(_receiveBuffer, cancellationToken).ConfigureAwait(false); + + await ms.WriteAsync(_receiveBuffer.Array!, _receiveBuffer.Offset, result.Count).ConfigureAwait(false); + } while (!result.EndOfMessage); + + ms.Seek(0, SeekOrigin.Begin); + + if (_logger.IsEnabled(LogEventLevel.Trace)) + { + _logger.Trace($"BIDI RCV << {Encoding.UTF8.GetString(ms.ToArray())}"); + } + + var res = await JsonSerializer.DeserializeAsync(ms, typeof(T), jsonSerializerOptions, cancellationToken).ConfigureAwait(false); + + return (T)res!; + } + + public async Task SendAsJsonAsync(Command command, JsonSerializerOptions jsonSerializerOptions, CancellationToken cancellationToken) + { + var buffer = JsonSerializer.SerializeToUtf8Bytes(command, typeof(Command), jsonSerializerOptions); + + if (_logger.IsEnabled(LogEventLevel.Trace)) + { + _logger.Trace($"BIDI SND >> {buffer.Length} > {Encoding.UTF8.GetString(buffer)}"); + } + + await _webSocket.SendAsync(new ArraySegment(buffer), WebSocketMessageType.Text, true, cancellationToken).ConfigureAwait(false); + } + + public void Dispose() + { + _webSocket.Dispose(); + } +} diff --git a/dotnet/src/webdriver/BiDi/EventArgs.cs b/dotnet/src/webdriver/BiDi/EventArgs.cs new file mode 100644 index 0000000000000..455cf31aac5e3 --- /dev/null +++ b/dotnet/src/webdriver/BiDi/EventArgs.cs @@ -0,0 +1,13 @@ +using OpenQA.Selenium.BiDi.Modules.BrowsingContext; +using System.Text.Json.Serialization; + +namespace OpenQA.Selenium.BiDi; + +public abstract record EventArgs(BiDi BiDi) +{ + [JsonIgnore] + public BiDi BiDi { get; internal set; } = BiDi; +} + +public abstract record BrowsingContextEventArgs(BiDi BiDi, BrowsingContext Context) + : EventArgs(BiDi); diff --git a/dotnet/src/webdriver/BiDi/Modules/Browser/BrowserModule.cs b/dotnet/src/webdriver/BiDi/Modules/Browser/BrowserModule.cs new file mode 100644 index 0000000000000..fb7cb001b86d6 --- /dev/null +++ b/dotnet/src/webdriver/BiDi/Modules/Browser/BrowserModule.cs @@ -0,0 +1,32 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using OpenQA.Selenium.BiDi.Communication; + +namespace OpenQA.Selenium.BiDi.Modules.Browser; + +public sealed class BrowserModule(Broker broker) : Module(broker) +{ + public async Task CloseAsync(CloseOptions? options = null) + { + await Broker.ExecuteCommandAsync(new CloseCommand(), options).ConfigureAwait(false); + } + + public async Task CreateUserContextAsync(CreateUserContextOptions? options = null) + { + return await Broker.ExecuteCommandAsync(new CreateUserContextCommand(), options).ConfigureAwait(false); + } + + public async Task> GetUserContextsAsync(GetUserContextsOptions? options = null) + { + var result = await Broker.ExecuteCommandAsync(new GetUserContextsCommand(), options).ConfigureAwait(false); + + return result.UserContexts; + } + + public async Task RemoveUserContextAsync(UserContext userContext, RemoveUserContextOptions? options = null) + { + var @params = new RemoveUserContextCommandParameters(userContext); + + await Broker.ExecuteCommandAsync(new RemoveUserContextCommand(@params), options).ConfigureAwait(false); + } +} diff --git a/dotnet/src/webdriver/BiDi/Modules/Browser/CloseCommand.cs b/dotnet/src/webdriver/BiDi/Modules/Browser/CloseCommand.cs new file mode 100644 index 0000000000000..40a68f42995dc --- /dev/null +++ b/dotnet/src/webdriver/BiDi/Modules/Browser/CloseCommand.cs @@ -0,0 +1,7 @@ +using OpenQA.Selenium.BiDi.Communication; + +namespace OpenQA.Selenium.BiDi.Modules.Browser; + +internal class CloseCommand() : Command(CommandParameters.Empty); + +public record CloseOptions : CommandOptions; \ No newline at end of file diff --git a/dotnet/src/webdriver/BiDi/Modules/Browser/CreateUserContextCommand.cs b/dotnet/src/webdriver/BiDi/Modules/Browser/CreateUserContextCommand.cs new file mode 100644 index 0000000000000..a962c59becc67 --- /dev/null +++ b/dotnet/src/webdriver/BiDi/Modules/Browser/CreateUserContextCommand.cs @@ -0,0 +1,7 @@ +using OpenQA.Selenium.BiDi.Communication; + +namespace OpenQA.Selenium.BiDi.Modules.Browser; + +internal class CreateUserContextCommand() : Command(CommandParameters.Empty); + +public record CreateUserContextOptions : CommandOptions; diff --git a/dotnet/src/webdriver/BiDi/Modules/Browser/GetUserContextsCommand.cs b/dotnet/src/webdriver/BiDi/Modules/Browser/GetUserContextsCommand.cs new file mode 100644 index 0000000000000..120743809d895 --- /dev/null +++ b/dotnet/src/webdriver/BiDi/Modules/Browser/GetUserContextsCommand.cs @@ -0,0 +1,10 @@ +using OpenQA.Selenium.BiDi.Communication; +using System.Collections.Generic; + +namespace OpenQA.Selenium.BiDi.Modules.Browser; + +internal class GetUserContextsCommand() : Command(CommandParameters.Empty); + +public record GetUserContextsOptions : CommandOptions; + +public record GetUserContextsResult(IReadOnlyList UserContexts); diff --git a/dotnet/src/webdriver/BiDi/Modules/Browser/RemoveUserContextCommand.cs b/dotnet/src/webdriver/BiDi/Modules/Browser/RemoveUserContextCommand.cs new file mode 100644 index 0000000000000..83fe80a633a50 --- /dev/null +++ b/dotnet/src/webdriver/BiDi/Modules/Browser/RemoveUserContextCommand.cs @@ -0,0 +1,9 @@ +using OpenQA.Selenium.BiDi.Communication; + +namespace OpenQA.Selenium.BiDi.Modules.Browser; + +internal class RemoveUserContextCommand(RemoveUserContextCommandParameters @params) : Command(@params); + +internal record RemoveUserContextCommandParameters(UserContext UserContext) : CommandParameters; + +public record RemoveUserContextOptions : CommandOptions; diff --git a/dotnet/src/webdriver/BiDi/Modules/Browser/UserContext.cs b/dotnet/src/webdriver/BiDi/Modules/Browser/UserContext.cs new file mode 100644 index 0000000000000..5d85ade48f8bf --- /dev/null +++ b/dotnet/src/webdriver/BiDi/Modules/Browser/UserContext.cs @@ -0,0 +1,27 @@ +using System; +using System.Threading.Tasks; + +namespace OpenQA.Selenium.BiDi.Modules.Browser; + +public class UserContext : IAsyncDisposable +{ + private readonly BiDi _bidi; + + internal UserContext(BiDi bidi, string id) + { + _bidi = bidi; + Id = id; + } + + public string Id { get; } + + public Task RemoveAsync() + { + return _bidi.Browser.RemoveUserContextAsync(this); + } + + public async ValueTask DisposeAsync() + { + await RemoveAsync().ConfigureAwait(false); + } +} diff --git a/dotnet/src/webdriver/BiDi/Modules/Browser/UserContextInfo.cs b/dotnet/src/webdriver/BiDi/Modules/Browser/UserContextInfo.cs new file mode 100644 index 0000000000000..eb72db166e02c --- /dev/null +++ b/dotnet/src/webdriver/BiDi/Modules/Browser/UserContextInfo.cs @@ -0,0 +1,3 @@ +namespace OpenQA.Selenium.BiDi.Modules.Browser; + +public record UserContextInfo(UserContext UserContext); diff --git a/dotnet/src/webdriver/BiDi/Modules/BrowsingContext/ActivateCommand.cs b/dotnet/src/webdriver/BiDi/Modules/BrowsingContext/ActivateCommand.cs new file mode 100644 index 0000000000000..94e13eaea1dfc --- /dev/null +++ b/dotnet/src/webdriver/BiDi/Modules/BrowsingContext/ActivateCommand.cs @@ -0,0 +1,9 @@ +using OpenQA.Selenium.BiDi.Communication; + +namespace OpenQA.Selenium.BiDi.Modules.BrowsingContext; + +internal class ActivateCommand(ActivateCommandParameters @params) : Command(@params); + +internal record ActivateCommandParameters(BrowsingContext Context) : CommandParameters; + +public record ActivateOptions : CommandOptions; diff --git a/dotnet/src/webdriver/BiDi/Modules/BrowsingContext/BrowsingContext.cs b/dotnet/src/webdriver/BiDi/Modules/BrowsingContext/BrowsingContext.cs new file mode 100644 index 0000000000000..4bc46f3a02bf0 --- /dev/null +++ b/dotnet/src/webdriver/BiDi/Modules/BrowsingContext/BrowsingContext.cs @@ -0,0 +1,184 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using System; + +namespace OpenQA.Selenium.BiDi.Modules.BrowsingContext; + +public class BrowsingContext +{ + internal BrowsingContext(BiDi bidi, string id) + { + _bidi = bidi; + Id = id; + + _logModule = new Lazy(() => new BrowsingContextLogModule(this, _bidi.Log)); + _networkModule = new Lazy(() => new BrowsingContextNetworkModule(this, _bidi.Network)); + _scriptModule = new Lazy(() => new BrowsingContextScriptModule(this, _bidi.ScriptModule)); + _storageModule = new Lazy(() => new BrowsingContextStorageModule(this, _bidi.Storage)); + _inputModule = new Lazy(() => new BrowsingContextInputModule(this, _bidi.InputModule)); + } + + private readonly BiDi _bidi; + + private readonly Lazy _logModule; + private readonly Lazy _networkModule; + private readonly Lazy _scriptModule; + private readonly Lazy _storageModule; + private readonly Lazy _inputModule; + + internal string Id { get; } + + public BrowsingContextLogModule Log => _logModule.Value; + + public BrowsingContextNetworkModule Network => _networkModule.Value; + + public BrowsingContextScriptModule Script => _scriptModule.Value; + + public BrowsingContextStorageModule Storage => _storageModule.Value; + + public BrowsingContextInputModule Input => _inputModule.Value; + + public Task NavigateAsync(string url, NavigateOptions? options = null) + { + return _bidi.BrowsingContextModule.NavigateAsync(this, url, options); + } + + public Task ReloadAsync(ReloadOptions? options = null) + { + return _bidi.BrowsingContextModule.ReloadAsync(this, options); + } + + public Task ActivateAsync(ActivateOptions? options = null) + { + return _bidi.BrowsingContextModule.ActivateAsync(this, options); + } + + public Task> LocateNodesAsync(Locator locator, LocateNodesOptions? options = null) + { + return _bidi.BrowsingContextModule.LocateNodesAsync(this, locator, options); + } + + public Task CaptureScreenshotAsync(CaptureScreenshotOptions? options = null) + { + return _bidi.BrowsingContextModule.CaptureScreenshotAsync(this, options); + } + + public Task CloseAsync(CloseOptions? options = null) + { + return _bidi.BrowsingContextModule.CloseAsync(this, options); + } + + public Task TraverseHistoryAsync(int delta, TraverseHistoryOptions? options = null) + { + return _bidi.BrowsingContextModule.TraverseHistoryAsync(this, delta, options); + } + + public Task NavigateBackAsync(TraverseHistoryOptions? options = null) + { + return TraverseHistoryAsync(-1, options); + } + + public Task NavigateForwardAsync(TraverseHistoryOptions? options = null) + { + return TraverseHistoryAsync(1, options); + } + + public Task SetViewportAsync(SetViewportOptions? options = null) + { + return _bidi.BrowsingContextModule.SetViewportAsync(this, options); + } + + public async Task PrintAsync(PrintOptions? options = null) + { + var result = await _bidi.BrowsingContextModule.PrintAsync(this, options).ConfigureAwait(false); + + return result.Data; + } + + public Task HandleUserPromptAsync(HandleUserPromptOptions? options = null) + { + return _bidi.BrowsingContextModule.HandleUserPromptAsync(this, options); + } + + public Task OnNavigationStartedAsync(Func handler, SubscriptionOptions? options = null) + { + return _bidi.BrowsingContextModule.OnNavigationStartedAsync(handler, new BrowsingContextsSubscriptionOptions(options) { Contexts = [this] }); + } + + public Task OnNavigationStartedAsync(Action handler, SubscriptionOptions? options = null) + { + return _bidi.BrowsingContextModule.OnNavigationStartedAsync(handler, new BrowsingContextsSubscriptionOptions(options) { Contexts = [this] }); + } + + public Task OnFragmentNavigatedAsync(Func handler, SubscriptionOptions? options = null) + { + return _bidi.BrowsingContextModule.OnFragmentNavigatedAsync(handler, new BrowsingContextsSubscriptionOptions(options) { Contexts = [this] }); + } + + public Task OnFragmentNavigatedAsync(Action handler, SubscriptionOptions? options = null) + { + return _bidi.BrowsingContextModule.OnFragmentNavigatedAsync(handler, new BrowsingContextsSubscriptionOptions(options) { Contexts = [this] }); + } + + public Task OnDomContentLoadedAsync(Func handler, SubscriptionOptions? options = null) + { + return _bidi.BrowsingContextModule.OnDomContentLoadedAsync(handler, new BrowsingContextsSubscriptionOptions(options) { Contexts = [this] }); + } + + public Task OnLoadAsync(Action handler, SubscriptionOptions? options = null) + { + return _bidi.BrowsingContextModule.OnLoadAsync(handler, new BrowsingContextsSubscriptionOptions(options) { Contexts = [this] }); + } + + public Task OnLoadAsync(Func handler, SubscriptionOptions? options = null) + { + return _bidi.BrowsingContextModule.OnLoadAsync(handler, new BrowsingContextsSubscriptionOptions(options) { Contexts = [this] }); + } + + public Task OnDownloadWillBeginAsync(Action handler, SubscriptionOptions? options = null) + { + return _bidi.BrowsingContextModule.OnDownloadWillBeginAsync(handler, new BrowsingContextsSubscriptionOptions(options) { Contexts = [this] }); + } + + public Task OnDownloadWillBeginAsync(Func handler, SubscriptionOptions? options = null) + { + return _bidi.BrowsingContextModule.OnDownloadWillBeginAsync(handler, new BrowsingContextsSubscriptionOptions(options) { Contexts = [this] }); + } + + public Task OnNavigationAbortedAsync(Action handler, SubscriptionOptions? options = null) + { + return _bidi.BrowsingContextModule.OnNavigationAbortedAsync(handler, new BrowsingContextsSubscriptionOptions(options) { Contexts = [this] }); + } + + public Task OnNavigationAbortedAsync(Func handler, SubscriptionOptions? options = null) + { + return _bidi.BrowsingContextModule.OnNavigationAbortedAsync(handler, new BrowsingContextsSubscriptionOptions(options) { Contexts = [this] }); + } + + public Task OnNavigationFailedAsync(Action handler, SubscriptionOptions? options = null) + { + return _bidi.BrowsingContextModule.OnNavigationFailedAsync(handler, new BrowsingContextsSubscriptionOptions(options) { Contexts = [this] }); + } + + public Task OnNavigationFailedAsync(Func handler, SubscriptionOptions? options = null) + { + return _bidi.BrowsingContextModule.OnNavigationFailedAsync(handler, new BrowsingContextsSubscriptionOptions(options) { Contexts = [this] }); + } + + public Task OnDomContentLoadedAsync(Action handler, SubscriptionOptions? options = null) + { + return _bidi.BrowsingContextModule.OnDomContentLoadedAsync(handler, new BrowsingContextsSubscriptionOptions(options) { Contexts = [this] }); + } + + public override bool Equals(object? obj) + { + if (obj is BrowsingContext browsingContextObj) return browsingContextObj.Id == Id; + + return false; + } + + public override int GetHashCode() + { + return Id.GetHashCode(); + } +} diff --git a/dotnet/src/webdriver/BiDi/Modules/BrowsingContext/BrowsingContextInfo.cs b/dotnet/src/webdriver/BiDi/Modules/BrowsingContext/BrowsingContextInfo.cs new file mode 100644 index 0000000000000..41b41bdf5df24 --- /dev/null +++ b/dotnet/src/webdriver/BiDi/Modules/BrowsingContext/BrowsingContextInfo.cs @@ -0,0 +1,12 @@ +using System.Collections.Generic; +using System.Text.Json.Serialization; + +namespace OpenQA.Selenium.BiDi.Modules.BrowsingContext; + +// TODO: Split it to separate class with just info and event args +public record BrowsingContextInfo(BiDi BiDi, IReadOnlyList Children, BrowsingContext Context, string Url, Browser.UserContext UserContext) + : BrowsingContextEventArgs(BiDi, Context) +{ + [JsonInclude] + public BrowsingContext? Parent { get; internal set; } +} diff --git a/dotnet/src/webdriver/BiDi/Modules/BrowsingContext/BrowsingContextInputModule.cs b/dotnet/src/webdriver/BiDi/Modules/BrowsingContext/BrowsingContextInputModule.cs new file mode 100644 index 0000000000000..2a8e92044e0f5 --- /dev/null +++ b/dotnet/src/webdriver/BiDi/Modules/BrowsingContext/BrowsingContextInputModule.cs @@ -0,0 +1,17 @@ +using System.Threading.Tasks; +using OpenQA.Selenium.BiDi.Modules.Input; +using System.Collections.Generic; + +namespace OpenQA.Selenium.BiDi.Modules.BrowsingContext; + +public class BrowsingContextInputModule(BrowsingContext context, InputModule inputModule) +{ + public Task PerformActionsAsync(IEnumerable actions, PerformActionsOptions? options = null) + { + options ??= new(); + + options.Actions = actions; + + return inputModule.PerformActionsAsync(context, options); + } +} diff --git a/dotnet/src/webdriver/BiDi/Modules/BrowsingContext/BrowsingContextLogModule.cs b/dotnet/src/webdriver/BiDi/Modules/BrowsingContext/BrowsingContextLogModule.cs new file mode 100644 index 0000000000000..938db7eea261a --- /dev/null +++ b/dotnet/src/webdriver/BiDi/Modules/BrowsingContext/BrowsingContextLogModule.cs @@ -0,0 +1,30 @@ +using OpenQA.Selenium.BiDi.Modules.Log; +using System.Threading.Tasks; +using System; + +namespace OpenQA.Selenium.BiDi.Modules.BrowsingContext; + +public class BrowsingContextLogModule(BrowsingContext context, LogModule logModule) +{ + public Task OnEntryAddedAsync(Func handler) + { + return logModule.OnEntryAddedAsync(async args => + { + if (args.Source.Context?.Equals(context) is true) + { + await handler(args).ConfigureAwait(false); + } + }); + } + + public Task OnEntryAddedAsync(Action handler) + { + return logModule.OnEntryAddedAsync(args => + { + if (args.Source.Context?.Equals(context) is true) + { + handler(args); + } + }); + } +} diff --git a/dotnet/src/webdriver/BiDi/Modules/BrowsingContext/BrowsingContextModule.cs b/dotnet/src/webdriver/BiDi/Modules/BrowsingContext/BrowsingContextModule.cs new file mode 100644 index 0000000000000..d734cfd7636e7 --- /dev/null +++ b/dotnet/src/webdriver/BiDi/Modules/BrowsingContext/BrowsingContextModule.cs @@ -0,0 +1,270 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using OpenQA.Selenium.BiDi.Communication; + +namespace OpenQA.Selenium.BiDi.Modules.BrowsingContext; + +public class BrowsingContextModule(Broker broker) : Module(broker) +{ + public async Task CreateAsync(BrowsingContextType type, CreateOptions? options = null) + { + var @params = new CreateCommandParameters(type); + + if (options is not null) + { + @params.ReferenceContext = options.ReferenceContext; + @params.Background = options.Background; + @params.UserContext = options.UserContext; + } + + var createResult = await Broker.ExecuteCommandAsync(new CreateCommand(@params), options).ConfigureAwait(false); + + return createResult.Context; + } + + public async Task NavigateAsync(BrowsingContext context, string url, NavigateOptions? options = null) + { + var @params = new NavigateCommandParameters(context, url); + + if (options is not null) + { + @params.Wait = options.Wait; + } + + return await Broker.ExecuteCommandAsync(new NavigateCommand(@params), options).ConfigureAwait(false); + } + + public async Task ActivateAsync(BrowsingContext context, ActivateOptions? options = null) + { + var @params = new ActivateCommandParameters(context); + + await Broker.ExecuteCommandAsync(new ActivateCommand(@params), options).ConfigureAwait(false); + } + + public async Task> LocateNodesAsync(BrowsingContext context, Locator locator, LocateNodesOptions? options = null) + { + var @params = new LocateNodesCommandParameters(context, locator); + + if (options is not null) + { + @params.MaxNodeCount = options.MaxNodeCount; + @params.SerializationOptions = options.SerializationOptions; + @params.StartNodes = options.StartNodes; + } + + var result = await Broker.ExecuteCommandAsync(new LocateNodesCommand(@params), options).ConfigureAwait(false); + + return result.Nodes; + } + + public async Task CaptureScreenshotAsync(BrowsingContext context, CaptureScreenshotOptions? options = null) + { + var @params = new CaptureScreenshotCommandParameters(context); + + if (options is not null) + { + @params.Origin = options.Origin; + @params.Format = options.Format; + @params.Clip = options.Clip; + } + + return await Broker.ExecuteCommandAsync(new CaptureScreenshotCommand(@params), options).ConfigureAwait(false); + } + + public async Task CloseAsync(BrowsingContext context, CloseOptions? options = null) + { + var @params = new CloseCommandParameters(context); + + await Broker.ExecuteCommandAsync(new CloseCommand(@params), options).ConfigureAwait(false); + } + + public async Task TraverseHistoryAsync(BrowsingContext context, int delta, TraverseHistoryOptions? options = null) + { + var @params = new TraverseHistoryCommandParameters(context, delta); + + return await Broker.ExecuteCommandAsync(new TraverseHistoryCommand(@params), options).ConfigureAwait(false); + } + + public async Task ReloadAsync(BrowsingContext context, ReloadOptions? options = null) + { + var @params = new ReloadCommandParameters(context); + + if (options is not null) + { + @params.IgnoreCache = options.IgnoreCache; + @params.Wait = options.Wait; + } + + return await Broker.ExecuteCommandAsync(new ReloadCommand(@params), options).ConfigureAwait(false); + } + + public async Task SetViewportAsync(BrowsingContext context, SetViewportOptions? options = null) + { + var @params = new SetViewportCommandParameters(context); + + if (options is not null) + { + @params.Viewport = options.Viewport; + @params.DevicePixelRatio = options?.DevicePixelRatio; + } + + await Broker.ExecuteCommandAsync(new SetViewportCommand(@params), options).ConfigureAwait(false); + } + + public async Task> GetTreeAsync(GetTreeOptions? options = null) + { + var @params = new GetTreeCommandParameters(); + + if (options is not null) + { + @params.MaxDepth = options.MaxDepth; + @params.Root = options.Root; + } + + var getTreeResult = await Broker.ExecuteCommandAsync(new GetTreeCommand(@params), options).ConfigureAwait(false); + + return getTreeResult.Contexts; + } + + public async Task PrintAsync(BrowsingContext context, PrintOptions? options = null) + { + var @params = new PrintCommandParameters(context); + + if (options is not null) + { + @params.Background = options.Background; + @params.Margin = options.Margin; + @params.Orientation = options.Orientation; + @params.Page = options.Page; + @params.PageRanges = options.PageRanges; + @params.Scale = options.Scale; + @params.ShrinkToFit = options.ShrinkToFit; + } + + return await Broker.ExecuteCommandAsync(new PrintCommand(@params), options).ConfigureAwait(false); + } + + public async Task HandleUserPromptAsync(BrowsingContext context, HandleUserPromptOptions? options = null) + { + var @params = new HandleUserPromptCommandParameters(context); + + if (options is not null) + { + @params.Accept = options.Accept; + @params.UserText = options.UserText; + } + + await Broker.ExecuteCommandAsync(new HandleUserPromptCommand(@params), options).ConfigureAwait(false); + } + + public async Task OnNavigationStartedAsync(Func handler, BrowsingContextsSubscriptionOptions? options = null) + { + return await Broker.SubscribeAsync("browsingContext.navigationStarted", handler, options).ConfigureAwait(false); + } + + public async Task OnNavigationStartedAsync(Action handler, BrowsingContextsSubscriptionOptions? options = null) + { + return await Broker.SubscribeAsync("browsingContext.navigationStarted", handler, options).ConfigureAwait(false); + } + + public async Task OnFragmentNavigatedAsync(Func handler, BrowsingContextsSubscriptionOptions? options = null) + { + return await Broker.SubscribeAsync("browsingContext.fragmentNavigated", handler, options).ConfigureAwait(false); + } + + public async Task OnFragmentNavigatedAsync(Action handler, BrowsingContextsSubscriptionOptions? options = null) + { + return await Broker.SubscribeAsync("browsingContext.fragmentNavigated", handler, options).ConfigureAwait(false); + } + + public async Task OnDomContentLoadedAsync(Func handler, BrowsingContextsSubscriptionOptions? options = null) + { + return await Broker.SubscribeAsync("browsingContext.domContentLoaded", handler, options).ConfigureAwait(false); + } + + public async Task OnDomContentLoadedAsync(Action handler, BrowsingContextsSubscriptionOptions? options = null) + { + return await Broker.SubscribeAsync("browsingContext.domContentLoaded", handler, options).ConfigureAwait(false); + } + + public async Task OnLoadAsync(Func handler, BrowsingContextsSubscriptionOptions? options = null) + { + return await Broker.SubscribeAsync("browsingContext.load", handler, options).ConfigureAwait(false); + } + + public async Task OnLoadAsync(Action handler, BrowsingContextsSubscriptionOptions? options = null) + { + return await Broker.SubscribeAsync("browsingContext.load", handler, options).ConfigureAwait(false); + } + + public async Task OnDownloadWillBeginAsync(Func handler, BrowsingContextsSubscriptionOptions? options = null) + { + return await Broker.SubscribeAsync("browsingContext.downloadWillBegin", handler, options).ConfigureAwait(false); + } + + public async Task OnDownloadWillBeginAsync(Action handler, BrowsingContextsSubscriptionOptions? options = null) + { + return await Broker.SubscribeAsync("browsingContext.downloadWillBegin", handler, options).ConfigureAwait(false); + } + + public async Task OnNavigationAbortedAsync(Func handler, BrowsingContextsSubscriptionOptions? options = null) + { + return await Broker.SubscribeAsync("browsingContext.navigationAborted", handler, options).ConfigureAwait(false); + } + + public async Task OnNavigationAbortedAsync(Action handler, BrowsingContextsSubscriptionOptions? options = null) + { + return await Broker.SubscribeAsync("browsingContext.navigationAborted", handler, options).ConfigureAwait(false); + } + + public async Task OnNavigationFailedAsync(Func handler, BrowsingContextsSubscriptionOptions? options = null) + { + return await Broker.SubscribeAsync("browsingContext.navigationFailed", handler, options).ConfigureAwait(false); + } + + public async Task OnNavigationFailedAsync(Action handler, BrowsingContextsSubscriptionOptions? options = null) + { + return await Broker.SubscribeAsync("browsingContext.navigationFailed", handler, options).ConfigureAwait(false); + } + + public async Task OnContextCreatedAsync(Func handler, BrowsingContextsSubscriptionOptions? options = null) + { + return await Broker.SubscribeAsync("browsingContext.contextCreated", handler, options).ConfigureAwait(false); + } + + public async Task OnContextCreatedAsync(Action handler, BrowsingContextsSubscriptionOptions? options = null) + { + return await Broker.SubscribeAsync("browsingContext.contextCreated", handler, options).ConfigureAwait(false); + } + + public async Task OnContextDestroyedAsync(Func handler, BrowsingContextsSubscriptionOptions? options = null) + { + return await Broker.SubscribeAsync("browsingContext.contextDestroyed", handler, options).ConfigureAwait(false); + } + + public async Task OnContextDestroyedAsync(Action handler, BrowsingContextsSubscriptionOptions? options = null) + { + return await Broker.SubscribeAsync("browsingContext.contextDestroyed", handler, options).ConfigureAwait(false); + } + + public async Task OnUserPromptOpenedAsync(Func handler, BrowsingContextsSubscriptionOptions? options = null) + { + return await Broker.SubscribeAsync("browsingContext.userPromptOpened", handler, options).ConfigureAwait(false); + } + + public async Task OnUserPromptOpenedAsync(Action handler, BrowsingContextsSubscriptionOptions? options = null) + { + return await Broker.SubscribeAsync("browsingContext.userPromptOpened", handler, options).ConfigureAwait(false); + } + + public async Task OnUserPromptClosedAsync(Func handler, BrowsingContextsSubscriptionOptions? options = null) + { + return await Broker.SubscribeAsync("browsingContext.userPromptClosed", handler, options).ConfigureAwait(false); + } + + public async Task OnUserPromptClosedAsync(Action handler, BrowsingContextsSubscriptionOptions? options = null) + { + return await Broker.SubscribeAsync("browsingContext.userPromptClosed", handler, options).ConfigureAwait(false); + } +} diff --git a/dotnet/src/webdriver/BiDi/Modules/BrowsingContext/BrowsingContextNetworkModule.cs b/dotnet/src/webdriver/BiDi/Modules/BrowsingContext/BrowsingContextNetworkModule.cs new file mode 100644 index 0000000000000..1979b15ba6368 --- /dev/null +++ b/dotnet/src/webdriver/BiDi/Modules/BrowsingContext/BrowsingContextNetworkModule.cs @@ -0,0 +1,92 @@ +using System.Threading.Tasks; +using System; +using OpenQA.Selenium.BiDi.Modules.Network; + +namespace OpenQA.Selenium.BiDi.Modules.BrowsingContext; + +public class BrowsingContextNetworkModule(BrowsingContext context, NetworkModule networkModule) +{ + public async Task InterceptRequestAsync(Func handler, AddInterceptOptions? interceptOptions = null, SubscriptionOptions? options = null) + { + interceptOptions ??= new(); + + interceptOptions.Contexts = [context]; + + var intercept = await networkModule.AddInterceptAsync([InterceptPhase.BeforeRequestSent], interceptOptions).ConfigureAwait(false); + + await intercept.OnBeforeRequestSentAsync(handler, new BrowsingContextsSubscriptionOptions(options) { Contexts = [context] }).ConfigureAwait(false); + + return intercept; + } + + public async Task InterceptResponseAsync(Func handler, AddInterceptOptions? interceptOptions = null, SubscriptionOptions? options = null) + { + interceptOptions ??= new(); + + interceptOptions.Contexts = [context]; + + var intercept = await networkModule.AddInterceptAsync([InterceptPhase.ResponseStarted], interceptOptions).ConfigureAwait(false); + + await intercept.OnResponseStartedAsync(handler, new BrowsingContextsSubscriptionOptions(options) { Contexts = [context] }).ConfigureAwait(false); + + return intercept; + } + + public async Task InterceptAuthenticationAsync(Func handler, AddInterceptOptions? interceptOptions = null, SubscriptionOptions? options = null) + { + interceptOptions ??= new(); + + interceptOptions.Contexts = [context]; + + var intercept = await networkModule.AddInterceptAsync([InterceptPhase.AuthRequired], interceptOptions).ConfigureAwait(false); + + await intercept.OnAuthRequiredAsync(handler, new BrowsingContextsSubscriptionOptions(options) { Contexts = [context] }).ConfigureAwait(false); + + return intercept; + } + + public Task OnBeforeRequestSentAsync(Func handler, SubscriptionOptions? options = null) + { + return networkModule.OnBeforeRequestSentAsync(handler, new BrowsingContextsSubscriptionOptions(options) { Contexts = [context] }); + } + + public Task OnBeforeRequestSentAsync(Action handler, SubscriptionOptions? options = null) + { + return networkModule.OnBeforeRequestSentAsync(handler, new BrowsingContextsSubscriptionOptions(options) { Contexts = [context] }); + } + + public Task OnResponseStartedAsync(Func handler, SubscriptionOptions? options = null) + { + return networkModule.OnResponseStartedAsync(handler, new BrowsingContextsSubscriptionOptions(options) { Contexts = [context] }); + } + + public Task OnResponseStartedAsync(Action handler, SubscriptionOptions? options = null) + { + return networkModule.OnResponseStartedAsync(handler, new BrowsingContextsSubscriptionOptions(options) { Contexts = [context] }); + } + + public Task OnResponseCompletedAsync(Func handler, SubscriptionOptions? options = null) + { + return networkModule.OnResponseCompletedAsync(handler, new BrowsingContextsSubscriptionOptions(options) { Contexts = [context] }); + } + + public Task OnResponseCompletedAsync(Action handler, SubscriptionOptions? options = null) + { + return networkModule.OnResponseCompletedAsync(handler, new BrowsingContextsSubscriptionOptions(options) { Contexts = [context] }); + } + + public Task OnFetchErrorAsync(Func handler, SubscriptionOptions? options = null) + { + return networkModule.OnFetchErrorAsync(handler, new BrowsingContextsSubscriptionOptions(options) { Contexts = [context] }); + } + + public Task OnFetchErrorAsync(Action handler, SubscriptionOptions? options = null) + { + return networkModule.OnFetchErrorAsync(handler, new BrowsingContextsSubscriptionOptions(options) { Contexts = [context] }); + } + + internal Task OnAuthRequiredAsync(Func handler, SubscriptionOptions? options = null) + { + return networkModule.OnAuthRequiredAsync(handler, new BrowsingContextsSubscriptionOptions(options) { Contexts = [context] }); + } +} diff --git a/dotnet/src/webdriver/BiDi/Modules/BrowsingContext/BrowsingContextScriptModule.cs b/dotnet/src/webdriver/BiDi/Modules/BrowsingContext/BrowsingContextScriptModule.cs new file mode 100644 index 0000000000000..89f24c939d202 --- /dev/null +++ b/dotnet/src/webdriver/BiDi/Modules/BrowsingContext/BrowsingContextScriptModule.cs @@ -0,0 +1,57 @@ +using System.Threading.Tasks; +using OpenQA.Selenium.BiDi.Modules.Script; +using System.Collections.Generic; + +namespace OpenQA.Selenium.BiDi.Modules.BrowsingContext; + +public class BrowsingContextScriptModule(BrowsingContext context, ScriptModule scriptModule) +{ + public async Task AddPreloadScriptAsync(string functionDeclaration, AddPreloadScriptOptions? options = null) + { + options ??= new(); + + options.Contexts = [context]; + + return await scriptModule.AddPreloadScriptAsync(functionDeclaration, options).ConfigureAwait(false); + } + + public async Task> GetRealmsAsync(GetRealmsOptions? options = null) + { + options ??= new(); + + options.Context = context; + + return await scriptModule.GetRealmsAsync(options).ConfigureAwait(false); + } + + public Task EvaluateAsync(string expression, bool awaitPromise, EvaluateOptions? options = null, ContextTargetOptions? targetOptions = null) + { + var contextTarget = new ContextTarget(context); + + if (targetOptions is not null) + { + contextTarget.Sandbox = targetOptions.Sandbox; + } + + return scriptModule.EvaluateAsync(expression, awaitPromise, contextTarget, options); + } + + public async Task EvaluateAsync(string expression, bool awaitPromise, EvaluateOptions? options = null) + { + var remoteValue = await EvaluateAsync(expression, awaitPromise, options).ConfigureAwait(false); + + return remoteValue.ConvertTo(); + } + + public Task CallFunctionAsync(string functionDeclaration, bool awaitPromise, CallFunctionOptions? options = null, ContextTargetOptions? targetOptions = null) + { + var contextTarget = new ContextTarget(context); + + if (targetOptions is not null) + { + contextTarget.Sandbox = targetOptions.Sandbox; + } + + return scriptModule.CallFunctionAsync(functionDeclaration, awaitPromise, contextTarget, options); + } +} diff --git a/dotnet/src/webdriver/BiDi/Modules/BrowsingContext/BrowsingContextStorageModule.cs b/dotnet/src/webdriver/BiDi/Modules/BrowsingContext/BrowsingContextStorageModule.cs new file mode 100644 index 0000000000000..bf4ac906e5382 --- /dev/null +++ b/dotnet/src/webdriver/BiDi/Modules/BrowsingContext/BrowsingContextStorageModule.cs @@ -0,0 +1,38 @@ +using System.Threading.Tasks; +using OpenQA.Selenium.BiDi.Modules.Storage; + +namespace OpenQA.Selenium.BiDi.Modules.BrowsingContext; + +public class BrowsingContextStorageModule(BrowsingContext context, StorageModule storageModule) +{ + public Task GetCookiesAsync(GetCookiesOptions? options = null) + { + options ??= new(); + + options.Partition = new BrowsingContextPartitionDescriptor(context); + + return storageModule.GetCookiesAsync(options); + } + + public async Task DeleteCookiesAsync(DeleteCookiesOptions? options = null) + { + options ??= new(); + + options.Partition = new BrowsingContextPartitionDescriptor(context); + + var res = await storageModule.DeleteCookiesAsync(options).ConfigureAwait(false); + + return res.PartitionKey; + } + + public async Task SetCookieAsync(PartialCookie cookie, SetCookieOptions? options = null) + { + options ??= new(); + + options.Partition = new BrowsingContextPartitionDescriptor(context); + + var res = await storageModule.SetCookieAsync(cookie, options).ConfigureAwait(false); + + return res.PartitionKey; + } +} diff --git a/dotnet/src/webdriver/BiDi/Modules/BrowsingContext/CaptureScreenshotCommand.cs b/dotnet/src/webdriver/BiDi/Modules/BrowsingContext/CaptureScreenshotCommand.cs new file mode 100644 index 0000000000000..ab144b5347a9f --- /dev/null +++ b/dotnet/src/webdriver/BiDi/Modules/BrowsingContext/CaptureScreenshotCommand.cs @@ -0,0 +1,52 @@ +using OpenQA.Selenium.BiDi.Communication; +using System.Text.Json.Serialization; + +namespace OpenQA.Selenium.BiDi.Modules.BrowsingContext; + +internal class CaptureScreenshotCommand(CaptureScreenshotCommandParameters @params) : Command(@params); + +internal record CaptureScreenshotCommandParameters(BrowsingContext Context) : CommandParameters +{ + public Origin? Origin { get; set; } + + public ImageFormat? Format { get; set; } + + public ClipRectangle? Clip { get; set; } +} + +public record CaptureScreenshotOptions : CommandOptions +{ + public Origin? Origin { get; set; } + + public ImageFormat? Format { get; set; } + + public ClipRectangle? Clip { get; set; } +} + +public enum Origin +{ + Viewport, + Document +} + +public record struct ImageFormat(string Type) +{ + public double? Quality { get; set; } +} + +[JsonPolymorphic(TypeDiscriminatorPropertyName = "type")] +[JsonDerivedType(typeof(BoxClipRectangle), "box")] +[JsonDerivedType(typeof(ElementClipRectangle), "element")] +public abstract record ClipRectangle; + +public record BoxClipRectangle(double X, double Y, double Width, double Height) : ClipRectangle; + +public record ElementClipRectangle(Script.SharedReference Element) : ClipRectangle; + +public record CaptureScreenshotResult(string Data) +{ + public byte[] AsBytes() + { + return System.Convert.FromBase64String(Data); + } +} diff --git a/dotnet/src/webdriver/BiDi/Modules/BrowsingContext/CloseCommand.cs b/dotnet/src/webdriver/BiDi/Modules/BrowsingContext/CloseCommand.cs new file mode 100644 index 0000000000000..7051d534dfcee --- /dev/null +++ b/dotnet/src/webdriver/BiDi/Modules/BrowsingContext/CloseCommand.cs @@ -0,0 +1,9 @@ +using OpenQA.Selenium.BiDi.Communication; + +namespace OpenQA.Selenium.BiDi.Modules.BrowsingContext; + +internal class CloseCommand(CloseCommandParameters @params) : Command(@params); + +internal record CloseCommandParameters(BrowsingContext Context) : CommandParameters; + +public record CloseOptions : CommandOptions; diff --git a/dotnet/src/webdriver/BiDi/Modules/BrowsingContext/CreateCommand.cs b/dotnet/src/webdriver/BiDi/Modules/BrowsingContext/CreateCommand.cs new file mode 100644 index 0000000000000..001b4bbfc2824 --- /dev/null +++ b/dotnet/src/webdriver/BiDi/Modules/BrowsingContext/CreateCommand.cs @@ -0,0 +1,31 @@ +using OpenQA.Selenium.BiDi.Communication; + +namespace OpenQA.Selenium.BiDi.Modules.BrowsingContext; + +internal class CreateCommand(CreateCommandParameters @params) : Command(@params); + +internal record CreateCommandParameters(BrowsingContextType Type) : CommandParameters +{ + public BrowsingContext? ReferenceContext { get; set; } + + public bool? Background { get; set; } + + public Browser.UserContext? UserContext { get; set; } +} + +public record CreateOptions : CommandOptions +{ + public BrowsingContext? ReferenceContext { get; set; } + + public bool? Background { get; set; } + + public Browser.UserContext? UserContext { get; set; } +} + +public enum BrowsingContextType +{ + Tab, + Window +} + +public record CreateResult(BrowsingContext Context); diff --git a/dotnet/src/webdriver/BiDi/Modules/BrowsingContext/GetTreeCommand.cs b/dotnet/src/webdriver/BiDi/Modules/BrowsingContext/GetTreeCommand.cs new file mode 100644 index 0000000000000..7e2717dc7c7ab --- /dev/null +++ b/dotnet/src/webdriver/BiDi/Modules/BrowsingContext/GetTreeCommand.cs @@ -0,0 +1,22 @@ +using OpenQA.Selenium.BiDi.Communication; +using System.Collections.Generic; + +namespace OpenQA.Selenium.BiDi.Modules.BrowsingContext; + +internal class GetTreeCommand(GetTreeCommandParameters @params) : Command(@params); + +internal record GetTreeCommandParameters : CommandParameters +{ + public long? MaxDepth { get; set; } + + public BrowsingContext? Root { get; set; } +} + +public record GetTreeOptions : CommandOptions +{ + public long? MaxDepth { get; set; } + + public BrowsingContext? Root { get; set; } +} + +public record GetTreeResult(IReadOnlyList Contexts); diff --git a/dotnet/src/webdriver/BiDi/Modules/BrowsingContext/HandleUserPromptCommand.cs b/dotnet/src/webdriver/BiDi/Modules/BrowsingContext/HandleUserPromptCommand.cs new file mode 100644 index 0000000000000..1d421ed67a062 --- /dev/null +++ b/dotnet/src/webdriver/BiDi/Modules/BrowsingContext/HandleUserPromptCommand.cs @@ -0,0 +1,19 @@ +using OpenQA.Selenium.BiDi.Communication; + +namespace OpenQA.Selenium.BiDi.Modules.BrowsingContext; + +class HandleUserPromptCommand(HandleUserPromptCommandParameters @params) : Command(@params); + +internal record HandleUserPromptCommandParameters(BrowsingContext Context) : CommandParameters +{ + public bool? Accept { get; set; } + + public string? UserText { get; set; } +} + +public record HandleUserPromptOptions : CommandOptions +{ + public bool? Accept { get; set; } + + public string? UserText { get; set; } +} diff --git a/dotnet/src/webdriver/BiDi/Modules/BrowsingContext/LocateNodesCommand.cs b/dotnet/src/webdriver/BiDi/Modules/BrowsingContext/LocateNodesCommand.cs new file mode 100644 index 0000000000000..6f39573a248c7 --- /dev/null +++ b/dotnet/src/webdriver/BiDi/Modules/BrowsingContext/LocateNodesCommand.cs @@ -0,0 +1,26 @@ +using OpenQA.Selenium.BiDi.Communication; +using System.Collections.Generic; + +namespace OpenQA.Selenium.BiDi.Modules.BrowsingContext; + +internal class LocateNodesCommand(LocateNodesCommandParameters @params) : Command(@params); + +internal record LocateNodesCommandParameters(BrowsingContext Context, Locator Locator) : CommandParameters +{ + public long? MaxNodeCount { get; set; } + + public Script.SerializationOptions? SerializationOptions { get; set; } + + public IEnumerable? StartNodes { get; set; } +} + +public record LocateNodesOptions : CommandOptions +{ + public long? MaxNodeCount { get; set; } + + public Script.SerializationOptions? SerializationOptions { get; set; } + + public IEnumerable? StartNodes { get; set; } +} + +public record LocateNodesResult(IReadOnlyList Nodes); diff --git a/dotnet/src/webdriver/BiDi/Modules/BrowsingContext/Locator.cs b/dotnet/src/webdriver/BiDi/Modules/BrowsingContext/Locator.cs new file mode 100644 index 0000000000000..fbe3f4bf2559b --- /dev/null +++ b/dotnet/src/webdriver/BiDi/Modules/BrowsingContext/Locator.cs @@ -0,0 +1,49 @@ +using System.Text.Json.Serialization; +using static OpenQA.Selenium.BiDi.Modules.BrowsingContext.AccessibilityLocator; + +namespace OpenQA.Selenium.BiDi.Modules.BrowsingContext; + +[JsonPolymorphic(TypeDiscriminatorPropertyName = "type")] +[JsonDerivedType(typeof(AccessibilityLocator), "accessibility")] +[JsonDerivedType(typeof(CssLocator), "css")] +[JsonDerivedType(typeof(InnerTextLocator), "innerText")] +[JsonDerivedType(typeof(XPathLocator), "xpath")] +public abstract record Locator +{ + public static CssLocator Css(string value) + => new(value); + + public static InnerTextLocator InnerText(string value, bool? ignoreCase = null, MatchType? matchType = null, long? maxDepth = null) + => new(value) { IgnoreCase = ignoreCase, MatchType = matchType, MaxDepth = maxDepth }; + + public static XPathLocator XPath(string value) + => new(value); +} + +public record AccessibilityLocator(AccessibilityValue Value) : Locator +{ + public record AccessibilityValue + { + public string? Name { get; set; } + public string? Role { get; set; } + } +} + +public record CssLocator(string Value) : Locator; + +public record InnerTextLocator(string Value) : Locator +{ + public bool? IgnoreCase { get; set; } + + public MatchType? MatchType { get; set; } + + public long? MaxDepth { get; set; } +} + +public enum MatchType +{ + Full, + Partial +} + +public record XPathLocator(string Value) : Locator; \ No newline at end of file diff --git a/dotnet/src/webdriver/BiDi/Modules/BrowsingContext/NavigateCommand.cs b/dotnet/src/webdriver/BiDi/Modules/BrowsingContext/NavigateCommand.cs new file mode 100644 index 0000000000000..f02fda8566bf7 --- /dev/null +++ b/dotnet/src/webdriver/BiDi/Modules/BrowsingContext/NavigateCommand.cs @@ -0,0 +1,24 @@ +using OpenQA.Selenium.BiDi.Communication; + +namespace OpenQA.Selenium.BiDi.Modules.BrowsingContext; + +internal class NavigateCommand(NavigateCommandParameters @params) : Command(@params); + +internal record NavigateCommandParameters(BrowsingContext Context, string Url) : CommandParameters +{ + public ReadinessState? Wait { get; set; } +} + +public record NavigateOptions : CommandOptions +{ + public ReadinessState? Wait { get; set; } +} + +public enum ReadinessState +{ + None, + Interactive, + Complete +} + +public record NavigateResult(Navigation Navigation, string Url); diff --git a/dotnet/src/webdriver/BiDi/Modules/BrowsingContext/Navigation.cs b/dotnet/src/webdriver/BiDi/Modules/BrowsingContext/Navigation.cs new file mode 100644 index 0000000000000..6333b24996261 --- /dev/null +++ b/dotnet/src/webdriver/BiDi/Modules/BrowsingContext/Navigation.cs @@ -0,0 +1,3 @@ +namespace OpenQA.Selenium.BiDi.Modules.BrowsingContext; + +public record Navigation(string Id); diff --git a/dotnet/src/webdriver/BiDi/Modules/BrowsingContext/NavigationInfo.cs b/dotnet/src/webdriver/BiDi/Modules/BrowsingContext/NavigationInfo.cs new file mode 100644 index 0000000000000..e2a92465bd41f --- /dev/null +++ b/dotnet/src/webdriver/BiDi/Modules/BrowsingContext/NavigationInfo.cs @@ -0,0 +1,6 @@ +using System; + +namespace OpenQA.Selenium.BiDi.Modules.BrowsingContext; + +public record NavigationInfo(BiDi BiDi, BrowsingContext Context, Navigation Navigation, DateTimeOffset Timestamp, string Url) + : BrowsingContextEventArgs(BiDi, Context); diff --git a/dotnet/src/webdriver/BiDi/Modules/BrowsingContext/PrintCommand.cs b/dotnet/src/webdriver/BiDi/Modules/BrowsingContext/PrintCommand.cs new file mode 100644 index 0000000000000..d926e83e4cc27 --- /dev/null +++ b/dotnet/src/webdriver/BiDi/Modules/BrowsingContext/PrintCommand.cs @@ -0,0 +1,68 @@ +using OpenQA.Selenium.BiDi.Communication; +using System.Collections.Generic; + +namespace OpenQA.Selenium.BiDi.Modules.BrowsingContext; + +internal class PrintCommand(PrintCommandParameters @params) : Command(@params); + +internal record PrintCommandParameters(BrowsingContext Context) : CommandParameters +{ + public bool? Background { get; set; } + + public Margin? Margin { get; set; } + + public Orientation? Orientation { get; set; } + + public Page? Page { get; set; } + + // TODO: It also supports strings + public IEnumerable? PageRanges { get; set; } + + public double? Scale { get; set; } + + public bool? ShrinkToFit { get; set; } +} + +public record PrintOptions : CommandOptions +{ + public bool? Background { get; set; } + + public Margin? Margin { get; set; } + + public Orientation? Orientation { get; set; } + + public Page? Page { get; set; } + + // TODO: It also supports strings + public IEnumerable? PageRanges { get; set; } + + public double? Scale { get; set; } + + public bool? ShrinkToFit { get; set; } +} + +public struct Margin +{ + public double? Bottom { get; set; } + + public double? Left { get; set; } + + public double? Right { get; set; } + + public double? Top { get; set; } +} + +public enum Orientation +{ + Portrait, + Landscape +} + +public struct Page +{ + public double? Height { get; set; } + + public double? Width { get; set; } +} + +public record PrintResult(string Data); diff --git a/dotnet/src/webdriver/BiDi/Modules/BrowsingContext/ReloadCommand.cs b/dotnet/src/webdriver/BiDi/Modules/BrowsingContext/ReloadCommand.cs new file mode 100644 index 0000000000000..2937a843c5a7f --- /dev/null +++ b/dotnet/src/webdriver/BiDi/Modules/BrowsingContext/ReloadCommand.cs @@ -0,0 +1,19 @@ +using OpenQA.Selenium.BiDi.Communication; + +namespace OpenQA.Selenium.BiDi.Modules.BrowsingContext; + +internal class ReloadCommand(ReloadCommandParameters @params) : Command(@params); + +internal record ReloadCommandParameters(BrowsingContext Context) : CommandParameters +{ + public bool? IgnoreCache { get; set; } + + public ReadinessState? Wait { get; set; } +} + +public record ReloadOptions : CommandOptions +{ + public bool? IgnoreCache { get; set; } + + public ReadinessState? Wait { get; set; } +} diff --git a/dotnet/src/webdriver/BiDi/Modules/BrowsingContext/SetViewportCommand.cs b/dotnet/src/webdriver/BiDi/Modules/BrowsingContext/SetViewportCommand.cs new file mode 100644 index 0000000000000..99d39cff45e68 --- /dev/null +++ b/dotnet/src/webdriver/BiDi/Modules/BrowsingContext/SetViewportCommand.cs @@ -0,0 +1,21 @@ +using OpenQA.Selenium.BiDi.Communication; + +namespace OpenQA.Selenium.BiDi.Modules.BrowsingContext; + +internal class SetViewportCommand(SetViewportCommandParameters @params) : Command(@params); + +internal record SetViewportCommandParameters(BrowsingContext Context) : CommandParameters +{ + public Viewport? Viewport { get; set; } + + public double? DevicePixelRatio { get; set; } +} + +public record SetViewportOptions : CommandOptions +{ + public Viewport? Viewport { get; set; } + + public double? DevicePixelRatio { get; set; } +} + +public readonly record struct Viewport(long Width, long Height); diff --git a/dotnet/src/webdriver/BiDi/Modules/BrowsingContext/TraverseHistoryCommand.cs b/dotnet/src/webdriver/BiDi/Modules/BrowsingContext/TraverseHistoryCommand.cs new file mode 100644 index 0000000000000..0f68afbb5df9b --- /dev/null +++ b/dotnet/src/webdriver/BiDi/Modules/BrowsingContext/TraverseHistoryCommand.cs @@ -0,0 +1,11 @@ +using OpenQA.Selenium.BiDi.Communication; + +namespace OpenQA.Selenium.BiDi.Modules.BrowsingContext; + +internal class TraverseHistoryCommand(TraverseHistoryCommandParameters @params) : Command(@params); + +internal record TraverseHistoryCommandParameters(BrowsingContext Context, long Delta) : CommandParameters; + +public record TraverseHistoryOptions : CommandOptions; + +public record TraverseHistoryResult; diff --git a/dotnet/src/webdriver/BiDi/Modules/BrowsingContext/UserPromptClosedEventArgs.cs b/dotnet/src/webdriver/BiDi/Modules/BrowsingContext/UserPromptClosedEventArgs.cs new file mode 100644 index 0000000000000..1a77c22ac8bb1 --- /dev/null +++ b/dotnet/src/webdriver/BiDi/Modules/BrowsingContext/UserPromptClosedEventArgs.cs @@ -0,0 +1,10 @@ +using System.Text.Json.Serialization; + +namespace OpenQA.Selenium.BiDi.Modules.BrowsingContext; + +public record UserPromptClosedEventArgs(BiDi BiDi, BrowsingContext Context, bool Accepted) + : BrowsingContextEventArgs(BiDi, Context) +{ + [JsonInclude] + public string? UserText { get; internal set; } +} diff --git a/dotnet/src/webdriver/BiDi/Modules/BrowsingContext/UserPromptOpenedEventArgs.cs b/dotnet/src/webdriver/BiDi/Modules/BrowsingContext/UserPromptOpenedEventArgs.cs new file mode 100644 index 0000000000000..f89e8bcad5ce1 --- /dev/null +++ b/dotnet/src/webdriver/BiDi/Modules/BrowsingContext/UserPromptOpenedEventArgs.cs @@ -0,0 +1,18 @@ +using System.Text.Json.Serialization; + +namespace OpenQA.Selenium.BiDi.Modules.BrowsingContext; + +public record UserPromptOpenedEventArgs(BiDi BiDi, BrowsingContext Context, UserPromptType Type, string Message) + : BrowsingContextEventArgs(BiDi, Context) +{ + [JsonInclude] + public string? DefaultValue { get; internal set; } +} + +public enum UserPromptType +{ + Alert, + Confirm, + Prompt, + BeforeUnload +} diff --git a/dotnet/src/webdriver/BiDi/Modules/Input/InputModule.cs b/dotnet/src/webdriver/BiDi/Modules/Input/InputModule.cs new file mode 100644 index 0000000000000..ff1aa85112a71 --- /dev/null +++ b/dotnet/src/webdriver/BiDi/Modules/Input/InputModule.cs @@ -0,0 +1,26 @@ +using OpenQA.Selenium.BiDi.Communication; +using System.Threading.Tasks; + +namespace OpenQA.Selenium.BiDi.Modules.Input; + +public sealed class InputModule(Broker broker) : Module(broker) +{ + public async Task PerformActionsAsync(BrowsingContext.BrowsingContext context, PerformActionsOptions? options = null) + { + var @params = new PerformActionsCommandParameters(context); + + if (options is not null) + { + @params.Actions = options.Actions; + } + + await Broker.ExecuteCommandAsync(new PerformActionsCommand(@params), options).ConfigureAwait(false); + } + + public async Task ReleaseActionsAsync(BrowsingContext.BrowsingContext context, ReleaseActionsOptions? options = null) + { + var @params = new ReleaseActionsCommandParameters(context); + + await Broker.ExecuteCommandAsync(new ReleaseActionsCommand(@params), options); + } +} diff --git a/dotnet/src/webdriver/BiDi/Modules/Input/PerformActionsCommand.cs b/dotnet/src/webdriver/BiDi/Modules/Input/PerformActionsCommand.cs new file mode 100644 index 0000000000000..49328d368569f --- /dev/null +++ b/dotnet/src/webdriver/BiDi/Modules/Input/PerformActionsCommand.cs @@ -0,0 +1,74 @@ +using OpenQA.Selenium.BiDi.Communication; +using System; +using System.Collections.Generic; +using System.Text.Json.Serialization; + +namespace OpenQA.Selenium.BiDi.Modules.Input; + +internal class PerformActionsCommand(PerformActionsCommandParameters @params) : Command(@params); + +internal record PerformActionsCommandParameters(BrowsingContext.BrowsingContext Context) : CommandParameters +{ + public IEnumerable? Actions { get; set; } +} + +public record PerformActionsOptions : CommandOptions +{ + public IEnumerable? Actions { get; set; } = []; +} + +[JsonPolymorphic(TypeDiscriminatorPropertyName = "type")] +[JsonDerivedType(typeof(KeySourceActions), "key")] +public abstract record SourceActions +{ + public static KeySourceActions Press(string text) + { + var keySourceActions = new KeySourceActions(); + + foreach (var character in text) + { + keySourceActions.Actions.AddRange([ + new KeyDownAction(character.ToString()), + new KeyUpAction(character.ToString()) + ]); + } + + return keySourceActions; + } +} + +public record KeySourceActions : SourceActions +{ + public string Id { get; set; } = Guid.NewGuid().ToString(); + + public List Actions { get; set; } = []; + + public new KeySourceActions Press(string text) + { + Actions.AddRange(SourceActions.Press(text).Actions); + + return this; + } + + public KeySourceActions Pause(long? duration = null) + { + Actions.Add(new KeyPauseAction { Duration = duration }); + + return this; + } +} + +[JsonPolymorphic(TypeDiscriminatorPropertyName = "type")] +[JsonDerivedType(typeof(KeyPauseAction), "pause")] +[JsonDerivedType(typeof(KeyDownAction), "keyDown")] +[JsonDerivedType(typeof(KeyUpAction), "keyUp")] +public abstract record KeySourceAction; + +public record KeyPauseAction : KeySourceAction +{ + public long? Duration { get; set; } +} + +public record KeyDownAction(string Value) : KeySourceAction; + +public record KeyUpAction(string Value) : KeySourceAction; diff --git a/dotnet/src/webdriver/BiDi/Modules/Input/ReleaseActionsCommand.cs b/dotnet/src/webdriver/BiDi/Modules/Input/ReleaseActionsCommand.cs new file mode 100644 index 0000000000000..1fec29c27902c --- /dev/null +++ b/dotnet/src/webdriver/BiDi/Modules/Input/ReleaseActionsCommand.cs @@ -0,0 +1,9 @@ +using OpenQA.Selenium.BiDi.Communication; + +namespace OpenQA.Selenium.BiDi.Modules.Input; + +internal class ReleaseActionsCommand(ReleaseActionsCommandParameters @params) : Command(@params); + +internal record ReleaseActionsCommandParameters(BrowsingContext.BrowsingContext Context) : CommandParameters; + +public record ReleaseActionsOptions : CommandOptions; diff --git a/dotnet/src/webdriver/BiDi/Modules/Log/LogEntry.cs b/dotnet/src/webdriver/BiDi/Modules/Log/LogEntry.cs new file mode 100644 index 0000000000000..ddc395ae21ebe --- /dev/null +++ b/dotnet/src/webdriver/BiDi/Modules/Log/LogEntry.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; + +namespace OpenQA.Selenium.BiDi.Modules.Log; + +// https://github.com/dotnet/runtime/issues/72604 +//[JsonPolymorphic(TypeDiscriminatorPropertyName = "type")] +//[JsonDerivedType(typeof(ConsoleLogEntry), "console")] +//[JsonDerivedType(typeof(JavascriptLogEntry), "javascript")] +public abstract record BaseLogEntry(BiDi BiDi, Level Level, Script.Source Source, string Text, DateTimeOffset Timestamp) + : EventArgs(BiDi); + +public record ConsoleLogEntry(BiDi BiDi, Level Level, Script.Source Source, string Text, DateTimeOffset Timestamp, string Method, IReadOnlyList Args) + : BaseLogEntry(BiDi, Level, Source, Text, Timestamp); + +public record JavascriptLogEntry(BiDi BiDi, Level Level, Script.Source Source, string Text, DateTimeOffset Timestamp) + : BaseLogEntry(BiDi, Level, Source, Text, Timestamp); + +public enum Level +{ + Debug, + Info, + Warn, + Error +} diff --git a/dotnet/src/webdriver/BiDi/Modules/Log/LogModule.cs b/dotnet/src/webdriver/BiDi/Modules/Log/LogModule.cs new file mode 100644 index 0000000000000..6491824c2b698 --- /dev/null +++ b/dotnet/src/webdriver/BiDi/Modules/Log/LogModule.cs @@ -0,0 +1,18 @@ +using System.Threading.Tasks; +using System; +using OpenQA.Selenium.BiDi.Communication; + +namespace OpenQA.Selenium.BiDi.Modules.Log; + +public sealed class LogModule(Broker broker) : Module(broker) +{ + public async Task OnEntryAddedAsync(Func handler, SubscriptionOptions? options = null) + { + return await Broker.SubscribeAsync("log.entryAdded", handler, options).ConfigureAwait(false); + } + + public async Task OnEntryAddedAsync(Action handler, SubscriptionOptions? options = null) + { + return await Broker.SubscribeAsync("log.entryAdded", handler, options).ConfigureAwait(false); + } +} diff --git a/dotnet/src/webdriver/BiDi/Modules/Module.cs b/dotnet/src/webdriver/BiDi/Modules/Module.cs new file mode 100644 index 0000000000000..0d1e66f5c0355 --- /dev/null +++ b/dotnet/src/webdriver/BiDi/Modules/Module.cs @@ -0,0 +1,8 @@ +using OpenQA.Selenium.BiDi.Communication; + +namespace OpenQA.Selenium.BiDi.Modules; + +public abstract class Module(Broker broker) +{ + protected Broker Broker { get; } = broker; +} diff --git a/dotnet/src/webdriver/BiDi/Modules/Network/AddInterceptCommand.cs b/dotnet/src/webdriver/BiDi/Modules/Network/AddInterceptCommand.cs new file mode 100644 index 0000000000000..78789feb6bbca --- /dev/null +++ b/dotnet/src/webdriver/BiDi/Modules/Network/AddInterceptCommand.cs @@ -0,0 +1,29 @@ +using System.Collections.Generic; +using OpenQA.Selenium.BiDi.Communication; + +namespace OpenQA.Selenium.BiDi.Modules.Network; + +internal class AddInterceptCommand(AddInterceptCommandParameters @params) : Command(@params); + +internal record AddInterceptCommandParameters(IEnumerable Phases) : CommandParameters +{ + public IEnumerable? Contexts { get; set; } + + public IEnumerable? UrlPatterns { get; set; } +} + +public record AddInterceptOptions : CommandOptions +{ + public IEnumerable? Contexts { get; set; } + + public IEnumerable? UrlPatterns { get; set; } +} + +public record AddInterceptResult(Intercept Intercept); + +public enum InterceptPhase +{ + BeforeRequestSent, + ResponseStarted, + AuthRequired +} diff --git a/dotnet/src/webdriver/BiDi/Modules/Network/AuthChallenge.cs b/dotnet/src/webdriver/BiDi/Modules/Network/AuthChallenge.cs new file mode 100644 index 0000000000000..c7eedcb7459bf --- /dev/null +++ b/dotnet/src/webdriver/BiDi/Modules/Network/AuthChallenge.cs @@ -0,0 +1,3 @@ +namespace OpenQA.Selenium.BiDi.Modules.Network; + +public record AuthChallenge(string Scheme, string Realm); diff --git a/dotnet/src/webdriver/BiDi/Modules/Network/AuthCredentials.cs b/dotnet/src/webdriver/BiDi/Modules/Network/AuthCredentials.cs new file mode 100644 index 0000000000000..f9aaaefde0257 --- /dev/null +++ b/dotnet/src/webdriver/BiDi/Modules/Network/AuthCredentials.cs @@ -0,0 +1,12 @@ +using System.Text.Json.Serialization; + +namespace OpenQA.Selenium.BiDi.Modules.Network; + +[JsonPolymorphic(TypeDiscriminatorPropertyName = "type")] +[JsonDerivedType(typeof(BasicAuthCredentials), "password")] +public abstract record AuthCredentials +{ + public static BasicAuthCredentials Basic(string username, string password) => new(username, password); +} + +public record BasicAuthCredentials(string Username, string Password) : AuthCredentials; diff --git a/dotnet/src/webdriver/BiDi/Modules/Network/AuthRequiredEventArgs.cs b/dotnet/src/webdriver/BiDi/Modules/Network/AuthRequiredEventArgs.cs new file mode 100644 index 0000000000000..6e3e8626e8b5f --- /dev/null +++ b/dotnet/src/webdriver/BiDi/Modules/Network/AuthRequiredEventArgs.cs @@ -0,0 +1,6 @@ +using System; + +namespace OpenQA.Selenium.BiDi.Modules.Network; + +public record AuthRequiredEventArgs(BiDi BiDi, BrowsingContext.BrowsingContext Context, bool IsBlocked, BrowsingContext.Navigation Navigation, long RedirectCount, RequestData Request, DateTimeOffset Timestamp, ResponseData Response) : + BaseParametersEventArgs(BiDi, Context, IsBlocked, Navigation, RedirectCount, Request, Timestamp); diff --git a/dotnet/src/webdriver/BiDi/Modules/Network/BaseParametersEventArgs.cs b/dotnet/src/webdriver/BiDi/Modules/Network/BaseParametersEventArgs.cs new file mode 100644 index 0000000000000..d632e433e137d --- /dev/null +++ b/dotnet/src/webdriver/BiDi/Modules/Network/BaseParametersEventArgs.cs @@ -0,0 +1,13 @@ +using System.Collections.Generic; +using System.Text.Json.Serialization; +using System; + +namespace OpenQA.Selenium.BiDi.Modules.Network; + +public abstract record BaseParametersEventArgs(BiDi BiDi, BrowsingContext.BrowsingContext Context, bool IsBlocked, BrowsingContext.Navigation Navigation, long RedirectCount, RequestData Request, DateTimeOffset Timestamp) + : BrowsingContextEventArgs(BiDi, Context) +{ + [JsonInclude] + public IReadOnlyList? Intercepts { get; internal set; } +} + diff --git a/dotnet/src/webdriver/BiDi/Modules/Network/BeforeRequestSentEventArgs.cs b/dotnet/src/webdriver/BiDi/Modules/Network/BeforeRequestSentEventArgs.cs new file mode 100644 index 0000000000000..0ca4994bd7bae --- /dev/null +++ b/dotnet/src/webdriver/BiDi/Modules/Network/BeforeRequestSentEventArgs.cs @@ -0,0 +1,7 @@ +using OpenQA.Selenium.BiDi.Modules.BrowsingContext; +using System; + +namespace OpenQA.Selenium.BiDi.Modules.Network; + +public record BeforeRequestSentEventArgs(BiDi BiDi, BrowsingContext.BrowsingContext Context, bool IsBlocked, Navigation Navigation, long RedirectCount, RequestData Request, DateTimeOffset Timestamp, Initiator Initiator) + : BaseParametersEventArgs(BiDi, Context, IsBlocked, Navigation, RedirectCount, Request, Timestamp); diff --git a/dotnet/src/webdriver/BiDi/Modules/Network/BytesValue.cs b/dotnet/src/webdriver/BiDi/Modules/Network/BytesValue.cs new file mode 100644 index 0000000000000..58a6bfac85d90 --- /dev/null +++ b/dotnet/src/webdriver/BiDi/Modules/Network/BytesValue.cs @@ -0,0 +1,15 @@ +using System.Text.Json.Serialization; + +namespace OpenQA.Selenium.BiDi.Modules.Network; + +[JsonPolymorphic(TypeDiscriminatorPropertyName = "type")] +[JsonDerivedType(typeof(StringValue), "string")] +[JsonDerivedType(typeof(Base64Value), "base64")] +public abstract record BytesValue +{ + public static implicit operator BytesValue(string value) => new StringValue(value); +} + +public record StringValue(string Value) : BytesValue; + +public record Base64Value(string Value) : BytesValue; diff --git a/dotnet/src/webdriver/BiDi/Modules/Network/ContinueRequestCommand.cs b/dotnet/src/webdriver/BiDi/Modules/Network/ContinueRequestCommand.cs new file mode 100644 index 0000000000000..2e0f298a44b9e --- /dev/null +++ b/dotnet/src/webdriver/BiDi/Modules/Network/ContinueRequestCommand.cs @@ -0,0 +1,32 @@ +using OpenQA.Selenium.BiDi.Communication; +using System.Collections.Generic; + +namespace OpenQA.Selenium.BiDi.Modules.Network; + +internal class ContinueRequestCommand(ContinueRequestCommandParameters @params) : Command(@params); + +internal record ContinueRequestCommandParameters(Request Request) : CommandParameters +{ + public BytesValue? Body { get; set; } + + public IEnumerable? Cookies { get; set; } + + public IEnumerable
? Headers { get; set; } + + public string? Method { get; set; } + + public string? Url { get; set; } +} + +public record ContinueRequestOptions : CommandOptions +{ + public BytesValue? Body { get; set; } + + public IEnumerable? Cookies { get; set; } + + public IEnumerable
? Headers { get; set; } + + public string? Method { get; set; } + + public string? Url { get; set; } +} diff --git a/dotnet/src/webdriver/BiDi/Modules/Network/ContinueResponseCommand.cs b/dotnet/src/webdriver/BiDi/Modules/Network/ContinueResponseCommand.cs new file mode 100644 index 0000000000000..dc9f038c36da5 --- /dev/null +++ b/dotnet/src/webdriver/BiDi/Modules/Network/ContinueResponseCommand.cs @@ -0,0 +1,32 @@ +using OpenQA.Selenium.BiDi.Communication; +using System.Collections.Generic; + +namespace OpenQA.Selenium.BiDi.Modules.Network; + +internal class ContinueResponseCommand(ContinueResponseCommandParameters @params) : Command(@params); + +internal record ContinueResponseCommandParameters(Request Request) : CommandParameters +{ + public IEnumerable? Cookies { get; set; } + + public IEnumerable? Credentials { get; set; } + + public IEnumerable
? Headers { get; set; } + + public string? ReasonPhrase { get; set; } + + public long? StatusCode { get; set; } +} + +public record ContinueResponseOptions : CommandOptions +{ + public IEnumerable? Cookies { get; set; } + + public IEnumerable? Credentials { get; set; } + + public IEnumerable
? Headers { get; set; } + + public string? ReasonPhrase { get; set; } + + public long? StatusCode { get; set; } +} diff --git a/dotnet/src/webdriver/BiDi/Modules/Network/ContinueWithAuthCommand.cs b/dotnet/src/webdriver/BiDi/Modules/Network/ContinueWithAuthCommand.cs new file mode 100644 index 0000000000000..848b95ed6c13d --- /dev/null +++ b/dotnet/src/webdriver/BiDi/Modules/Network/ContinueWithAuthCommand.cs @@ -0,0 +1,24 @@ +using OpenQA.Selenium.BiDi.Communication; +using System.Text.Json.Serialization; + +namespace OpenQA.Selenium.BiDi.Modules.Network; + +internal class ContinueWithAuthCommand(ContinueWithAuthParameters @params) : Command(@params); + +[JsonPolymorphic(TypeDiscriminatorPropertyName = "action")] +[JsonDerivedType(typeof(ContinueWithAuthCredentials), "provideCredentials")] +[JsonDerivedType(typeof(ContinueWithDefaultAuth), "default")] +[JsonDerivedType(typeof(ContinueWithCancelledAuth), "cancel")] +internal abstract record ContinueWithAuthParameters(Request Request) : CommandParameters; + +internal record ContinueWithAuthCredentials(Request Request, AuthCredentials Credentials) : ContinueWithAuthParameters(Request); + +internal record ContinueWithDefaultAuth(Request Request) : ContinueWithAuthParameters(Request); + +internal record ContinueWithCancelledAuth(Request Request) : ContinueWithAuthParameters(Request); + +public record ContinueWithAuthOptions : CommandOptions; + +public record ContinueWithDefaultAuthOptions : CommandOptions; + +public record ContinueWithCancelledAuthOptions : CommandOptions; diff --git a/dotnet/src/webdriver/BiDi/Modules/Network/Cookie.cs b/dotnet/src/webdriver/BiDi/Modules/Network/Cookie.cs new file mode 100644 index 0000000000000..c4380259a6be2 --- /dev/null +++ b/dotnet/src/webdriver/BiDi/Modules/Network/Cookie.cs @@ -0,0 +1,17 @@ +using System; +using System.Text.Json.Serialization; + +namespace OpenQA.Selenium.BiDi.Modules.Network; + +public record Cookie(string Name, BytesValue Value, string Domain, string Path, long Size, bool HttpOnly, bool Secure, SameSite SameSite) +{ + [JsonInclude] + public DateTimeOffset? Expiry { get; internal set; } +} + +public enum SameSite +{ + Strict, + Lax, + None +} diff --git a/dotnet/src/webdriver/BiDi/Modules/Network/CookieHeader.cs b/dotnet/src/webdriver/BiDi/Modules/Network/CookieHeader.cs new file mode 100644 index 0000000000000..1dca3a259ddb1 --- /dev/null +++ b/dotnet/src/webdriver/BiDi/Modules/Network/CookieHeader.cs @@ -0,0 +1,3 @@ +namespace OpenQA.Selenium.BiDi.Modules.Network; + +public record CookieHeader(string Name, BytesValue Value); diff --git a/dotnet/src/webdriver/BiDi/Modules/Network/FailRequestCommand.cs b/dotnet/src/webdriver/BiDi/Modules/Network/FailRequestCommand.cs new file mode 100644 index 0000000000000..e7d604121bce1 --- /dev/null +++ b/dotnet/src/webdriver/BiDi/Modules/Network/FailRequestCommand.cs @@ -0,0 +1,9 @@ +using OpenQA.Selenium.BiDi.Communication; + +namespace OpenQA.Selenium.BiDi.Modules.Network; + +internal class FailRequestCommand(FailRequestCommandParameters @params) : Command(@params); + +internal record FailRequestCommandParameters(Request Request) : CommandParameters; + +public record FailRequestOptions : CommandOptions; diff --git a/dotnet/src/webdriver/BiDi/Modules/Network/FetchErrorEventArgs.cs b/dotnet/src/webdriver/BiDi/Modules/Network/FetchErrorEventArgs.cs new file mode 100644 index 0000000000000..93fde5e00899a --- /dev/null +++ b/dotnet/src/webdriver/BiDi/Modules/Network/FetchErrorEventArgs.cs @@ -0,0 +1,7 @@ +using OpenQA.Selenium.BiDi.Modules.BrowsingContext; +using System; + +namespace OpenQA.Selenium.BiDi.Modules.Network; + +public record FetchErrorEventArgs(BiDi BiDi, BrowsingContext.BrowsingContext Context, bool IsBlocked, Navigation Navigation, long RedirectCount, RequestData Request, DateTimeOffset Timestamp, string ErrorText) + : BaseParametersEventArgs(BiDi, Context, IsBlocked, Navigation, RedirectCount, Request, Timestamp); diff --git a/dotnet/src/webdriver/BiDi/Modules/Network/FetchTimingInfo.cs b/dotnet/src/webdriver/BiDi/Modules/Network/FetchTimingInfo.cs new file mode 100644 index 0000000000000..f7e88af80bd87 --- /dev/null +++ b/dotnet/src/webdriver/BiDi/Modules/Network/FetchTimingInfo.cs @@ -0,0 +1,15 @@ +namespace OpenQA.Selenium.BiDi.Modules.Network; + +public record FetchTimingInfo(double TimeOrigin, + double RequestTime, + double RedirectStart, + double RedirectEnd, + double FetchStart, + double DnsStart, + double DnsEnd, + double ConnectStart, + double ConnectEnd, + double TlsStart, + double RequestStart, + double ResponseStart, + double ResponseEnd); \ No newline at end of file diff --git a/dotnet/src/webdriver/BiDi/Modules/Network/Header.cs b/dotnet/src/webdriver/BiDi/Modules/Network/Header.cs new file mode 100644 index 0000000000000..f7ae1560735df --- /dev/null +++ b/dotnet/src/webdriver/BiDi/Modules/Network/Header.cs @@ -0,0 +1,3 @@ +namespace OpenQA.Selenium.BiDi.Modules.Network; + +public record Header(string Name, BytesValue Value); \ No newline at end of file diff --git a/dotnet/src/webdriver/BiDi/Modules/Network/Initiator.cs b/dotnet/src/webdriver/BiDi/Modules/Network/Initiator.cs new file mode 100644 index 0000000000000..df2bfffb3a479 --- /dev/null +++ b/dotnet/src/webdriver/BiDi/Modules/Network/Initiator.cs @@ -0,0 +1,20 @@ +namespace OpenQA.Selenium.BiDi.Modules.Network; + +public record Initiator(InitiatorType Type) +{ + public long? ColumnNumber { get; set; } + + public long? LineNumber { get; set; } + + public Script.StackTrace? StackTrace { get; set; } + + public Request? Request { get; set; } +} + +public enum InitiatorType +{ + Parser, + Script, + Preflight, + Other +} \ No newline at end of file diff --git a/dotnet/src/webdriver/BiDi/Modules/Network/Intercept.cs b/dotnet/src/webdriver/BiDi/Modules/Network/Intercept.cs new file mode 100644 index 0000000000000..a64507d4c4f24 --- /dev/null +++ b/dotnet/src/webdriver/BiDi/Modules/Network/Intercept.cs @@ -0,0 +1,105 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace OpenQA.Selenium.BiDi.Modules.Network; + +public class Intercept : IAsyncDisposable +{ + private readonly BiDi _bidi; + + protected readonly IList _onBeforeRequestSentSubscriptions = []; + protected readonly IList _onResponseStartedSubscriptions = []; + protected readonly IList _onAuthRequiredSubscriptions = []; + + internal Intercept(BiDi bidi, string id) + { + _bidi = bidi; + Id = id; + } + + public string Id { get; } + + public async Task RemoveAsync() + { + await _bidi.Network.RemoveInterceptAsync(this).ConfigureAwait(false); + + foreach (var subscription in _onBeforeRequestSentSubscriptions) + { + await subscription.UnsubscribeAsync().ConfigureAwait(false); + } + + foreach (var subscription in _onResponseStartedSubscriptions) + { + await subscription.UnsubscribeAsync().ConfigureAwait(false); + } + + foreach (var subscription in _onAuthRequiredSubscriptions) + { + await subscription.UnsubscribeAsync().ConfigureAwait(false); + } + } + + public async Task OnBeforeRequestSentAsync(Func handler, SubscriptionOptions? options = null) + { + var subscription = await _bidi.Network.OnBeforeRequestSentAsync(async args => await Filter(args, handler), options).ConfigureAwait(false); + + _onBeforeRequestSentSubscriptions.Add(subscription); + } + + public async Task OnResponseStartedAsync(Func handler, SubscriptionOptions? options = null) + { + var subscription = await _bidi.Network.OnResponseStartedAsync(async args => await Filter(args, handler), options).ConfigureAwait(false); + + _onResponseStartedSubscriptions.Add(subscription); + } + + public async Task OnAuthRequiredAsync(Func handler, SubscriptionOptions? options = null) + { + var subscription = await _bidi.Network.OnAuthRequiredAsync(async args => await Filter(args, handler), options).ConfigureAwait(false); + + _onAuthRequiredSubscriptions.Add(subscription); + } + + private async Task Filter(BeforeRequestSentEventArgs args, Func handler) + { + if (args.Intercepts?.Contains(this) is true && args.IsBlocked) + { + await handler(args).ConfigureAwait(false); + } + } + + private async Task Filter(ResponseStartedEventArgs args, Func handler) + { + if (args.Intercepts?.Contains(this) is true && args.IsBlocked) + { + await handler(args).ConfigureAwait(false); + } + } + + private async Task Filter(AuthRequiredEventArgs args, Func handler) + { + if (args.Intercepts?.Contains(this) is true && args.IsBlocked) + { + await handler(args).ConfigureAwait(false); + } + } + + public async ValueTask DisposeAsync() + { + await RemoveAsync(); + } + + public override bool Equals(object? obj) + { + if (obj is Intercept interceptObj) return interceptObj.Id == Id; + + return false; + } + + public override int GetHashCode() + { + return Id.GetHashCode(); + } +} diff --git a/dotnet/src/webdriver/BiDi/Modules/Network/NetworkModule.cs b/dotnet/src/webdriver/BiDi/Modules/Network/NetworkModule.cs new file mode 100644 index 0000000000000..65264dbf51a67 --- /dev/null +++ b/dotnet/src/webdriver/BiDi/Modules/Network/NetworkModule.cs @@ -0,0 +1,173 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using OpenQA.Selenium.BiDi.Communication; + +namespace OpenQA.Selenium.BiDi.Modules.Network; + +public sealed class NetworkModule(Broker broker) : Module(broker) +{ + internal async Task AddInterceptAsync(IEnumerable phases, AddInterceptOptions? options = null) + { + var @params = new AddInterceptCommandParameters(phases); + + if (options is not null) + { + @params.Contexts = options.Contexts; + @params.UrlPatterns = options.UrlPatterns; + } + + var result = await Broker.ExecuteCommandAsync(new AddInterceptCommand(@params), options).ConfigureAwait(false); + + return result.Intercept; + } + + internal async Task RemoveInterceptAsync(Intercept intercept, RemoveInterceptOptions? options = null) + { + var @params = new RemoveInterceptCommandParameters(intercept); + + await Broker.ExecuteCommandAsync(new RemoveInterceptCommand(@params), options).ConfigureAwait(false); + } + + public async Task InterceptRequestAsync(Func handler, AddInterceptOptions? interceptOptions = null, SubscriptionOptions? options = null) + { + var intercept = await AddInterceptAsync([InterceptPhase.BeforeRequestSent], interceptOptions).ConfigureAwait(false); + + await intercept.OnBeforeRequestSentAsync(handler, options).ConfigureAwait(false); + + return intercept; + } + + public async Task InterceptResponseAsync(Func handler, AddInterceptOptions? interceptOptions = null, SubscriptionOptions? options = null) + { + var intercept = await AddInterceptAsync([InterceptPhase.ResponseStarted], interceptOptions).ConfigureAwait(false); + + await intercept.OnResponseStartedAsync(handler, options).ConfigureAwait(false); + + return intercept; + } + + public async Task InterceptAuthenticationAsync(Func handler, AddInterceptOptions? interceptOptions = null, SubscriptionOptions? options = null) + { + var intercept = await AddInterceptAsync([InterceptPhase.AuthRequired], interceptOptions).ConfigureAwait(false); + + await intercept.OnAuthRequiredAsync(handler, options).ConfigureAwait(false); + + return intercept; + } + + internal async Task ContinueRequestAsync(Request request, ContinueRequestOptions? options = null) + { + var @params = new ContinueRequestCommandParameters(request); + + if (options is not null) + { + @params.Body = options.Body; + @params.Cookies = options.Cookies; + @params.Headers = options.Headers; + @params.Method = options.Method; + @params.Url = options.Url; + } + + await Broker.ExecuteCommandAsync(new ContinueRequestCommand(@params), options).ConfigureAwait(false); + } + + internal async Task ContinueResponseAsync(Request request, ContinueResponseOptions? options = null) + { + var @params = new ContinueResponseCommandParameters(request); + + if (options is not null) + { + @params.Cookies = options.Cookies; + @params.Credentials = options.Credentials; + @params.Headers = options.Headers; + @params.ReasonPhrase = options.ReasonPhrase; + @params.StatusCode = options.StatusCode; + } + + await Broker.ExecuteCommandAsync(new ContinueResponseCommand(@params), options).ConfigureAwait(false); + } + + internal async Task FailRequestAsync(Request request, FailRequestOptions? options = null) + { + var @params = new FailRequestCommandParameters(request); + + await Broker.ExecuteCommandAsync(new FailRequestCommand(@params), options).ConfigureAwait(false); + } + + internal async Task ProvideResponseAsync(Request request, ProvideResponseOptions? options = null) + { + var @params = new ProvideResponseCommandParameters(request); + + if (options is not null) + { + @params.Body = options.Body; + @params.Cookies = options.Cookies; + @params.Headers = options.Headers; + @params.ReasonPhrase = options.ReasonPhrase; + @params.StatusCode = options.StatusCode; + } + + await Broker.ExecuteCommandAsync(new ProvideResponseCommand(@params), options).ConfigureAwait(false); + } + + internal async Task ContinueWithAuthAsync(Request request, AuthCredentials credentials, ContinueWithAuthOptions? options = null) + { + await Broker.ExecuteCommandAsync(new ContinueWithAuthCommand(new ContinueWithAuthCredentials(request, credentials)), options).ConfigureAwait(false); + } + + internal async Task ContinueWithAuthAsync(Request request, ContinueWithDefaultAuthOptions? options = null) + { + await Broker.ExecuteCommandAsync(new ContinueWithAuthCommand(new ContinueWithDefaultAuth(request)), options).ConfigureAwait(false); + } + + internal async Task ContinueWithAuthAsync(Request request, ContinueWithCancelledAuthOptions? options = null) + { + await Broker.ExecuteCommandAsync(new ContinueWithAuthCommand(new ContinueWithCancelledAuth(request)), options).ConfigureAwait(false); + } + + public async Task OnBeforeRequestSentAsync(Func handler, SubscriptionOptions? options = null) + { + return await Broker.SubscribeAsync("network.beforeRequestSent", handler, options).ConfigureAwait(false); + } + + public async Task OnBeforeRequestSentAsync(Action handler, SubscriptionOptions? options = null) + { + return await Broker.SubscribeAsync("network.beforeRequestSent", handler, options).ConfigureAwait(false); + } + + public async Task OnResponseStartedAsync(Func handler, SubscriptionOptions? options = null) + { + return await Broker.SubscribeAsync("network.responseStarted", handler, options).ConfigureAwait(false); + } + + public async Task OnResponseStartedAsync(Action handler, SubscriptionOptions? options = null) + { + return await Broker.SubscribeAsync("network.responseStarted", handler, options).ConfigureAwait(false); + } + + public async Task OnResponseCompletedAsync(Func handler, SubscriptionOptions? options = null) + { + return await Broker.SubscribeAsync("network.responseCompleted", handler, options).ConfigureAwait(false); + } + + public async Task OnResponseCompletedAsync(Action handler, SubscriptionOptions? options = null) + { + return await Broker.SubscribeAsync("network.responseCompleted", handler, options).ConfigureAwait(false); + } + + public async Task OnFetchErrorAsync(Func handler, SubscriptionOptions? options = null) + { + return await Broker.SubscribeAsync("network.fetchError", handler, options).ConfigureAwait(false); + } + + public async Task OnFetchErrorAsync(Action handler, SubscriptionOptions? options = null) + { + return await Broker.SubscribeAsync("network.fetchError", handler, options).ConfigureAwait(false); + } + + internal async Task OnAuthRequiredAsync(Func handler, SubscriptionOptions? options = null) + { + return await Broker.SubscribeAsync("network.authRequired", handler, options).ConfigureAwait(false); + } +} diff --git a/dotnet/src/webdriver/BiDi/Modules/Network/ProvideResponseCommand.cs b/dotnet/src/webdriver/BiDi/Modules/Network/ProvideResponseCommand.cs new file mode 100644 index 0000000000000..a7cfbd6c3e1e6 --- /dev/null +++ b/dotnet/src/webdriver/BiDi/Modules/Network/ProvideResponseCommand.cs @@ -0,0 +1,32 @@ +using OpenQA.Selenium.BiDi.Communication; +using System.Collections.Generic; + +namespace OpenQA.Selenium.BiDi.Modules.Network; + +internal class ProvideResponseCommand(ProvideResponseCommandParameters @params) : Command(@params); + +internal record ProvideResponseCommandParameters(Request Request) : CommandParameters +{ + public BytesValue? Body { get; set; } + + public IEnumerable? Cookies { get; set; } + + public IEnumerable
? Headers { get; set; } + + public string? ReasonPhrase { get; set; } + + public long? StatusCode { get; set; } +} + +public record ProvideResponseOptions : CommandOptions +{ + public BytesValue? Body { get; set; } + + public IEnumerable? Cookies { get; set; } + + public IEnumerable
? Headers { get; set; } + + public string? ReasonPhrase { get; set; } + + public long? StatusCode { get; set; } +} diff --git a/dotnet/src/webdriver/BiDi/Modules/Network/RemoveInterceptCommand.cs b/dotnet/src/webdriver/BiDi/Modules/Network/RemoveInterceptCommand.cs new file mode 100644 index 0000000000000..f59c0bd64a3c1 --- /dev/null +++ b/dotnet/src/webdriver/BiDi/Modules/Network/RemoveInterceptCommand.cs @@ -0,0 +1,9 @@ +using OpenQA.Selenium.BiDi.Communication; + +namespace OpenQA.Selenium.BiDi.Modules.Network; + +internal class RemoveInterceptCommand(RemoveInterceptCommandParameters @params) : Command(@params); + +internal record RemoveInterceptCommandParameters(Intercept Intercept) : CommandParameters; + +public record RemoveInterceptOptions : CommandOptions; diff --git a/dotnet/src/webdriver/BiDi/Modules/Network/Request.cs b/dotnet/src/webdriver/BiDi/Modules/Network/Request.cs new file mode 100644 index 0000000000000..1e99657a469a6 --- /dev/null +++ b/dotnet/src/webdriver/BiDi/Modules/Network/Request.cs @@ -0,0 +1,51 @@ +using System.Threading.Tasks; + +namespace OpenQA.Selenium.BiDi.Modules.Network; + +public class Request +{ + private readonly BiDi _bidi; + + internal Request(BiDi bidi, string id) + { + _bidi = bidi; + Id = id; + } + + public string Id { get; private set; } + + public Task ContinueAsync(ContinueRequestOptions? options = null) + { + return _bidi.Network.ContinueRequestAsync(this, options); + } + + public Task FailAsync() + { + return _bidi.Network.FailRequestAsync(this); + } + + public Task ProvideResponseAsync(ProvideResponseOptions? options = null) + { + return _bidi.Network.ProvideResponseAsync(this, options); + } + + public Task ContinueResponseAsync(ContinueResponseOptions? options = null) + { + return _bidi.Network.ContinueResponseAsync(this, options); + } + + public Task ContinueWithAuthAsync(AuthCredentials credentials, ContinueWithAuthOptions? options = null) + { + return _bidi.Network.ContinueWithAuthAsync(this, credentials, options); + } + + public Task ContinueWithAuthAsync(ContinueWithDefaultAuthOptions? options = null) + { + return _bidi.Network.ContinueWithAuthAsync(this, options); + } + + public Task ContinueWithAuthAsync(ContinueWithCancelledAuthOptions? options = null) + { + return _bidi.Network.ContinueWithAuthAsync(this, options); + } +} diff --git a/dotnet/src/webdriver/BiDi/Modules/Network/RequestData.cs b/dotnet/src/webdriver/BiDi/Modules/Network/RequestData.cs new file mode 100644 index 0000000000000..ed08db0befee8 --- /dev/null +++ b/dotnet/src/webdriver/BiDi/Modules/Network/RequestData.cs @@ -0,0 +1,5 @@ +using System.Collections.Generic; + +namespace OpenQA.Selenium.BiDi.Modules.Network; + +public record RequestData(Request Request, string Url, string Method, IReadOnlyList
Headers, IReadOnlyList Cookies, long HeadersSize, long? BodySize, FetchTimingInfo Timings); diff --git a/dotnet/src/webdriver/BiDi/Modules/Network/ResponseCompletedEventArgs.cs b/dotnet/src/webdriver/BiDi/Modules/Network/ResponseCompletedEventArgs.cs new file mode 100644 index 0000000000000..61bc806f1edcf --- /dev/null +++ b/dotnet/src/webdriver/BiDi/Modules/Network/ResponseCompletedEventArgs.cs @@ -0,0 +1,7 @@ +using OpenQA.Selenium.BiDi.Modules.BrowsingContext; +using System; + +namespace OpenQA.Selenium.BiDi.Modules.Network; + +public record ResponseCompletedEventArgs(BiDi BiDi, BrowsingContext.BrowsingContext Context, bool IsBlocked, Navigation Navigation, long RedirectCount, RequestData Request, DateTimeOffset Timestamp, ResponseData Response) + : BaseParametersEventArgs(BiDi, Context, IsBlocked, Navigation, RedirectCount, Request, Timestamp); diff --git a/dotnet/src/webdriver/BiDi/Modules/Network/ResponseContent.cs b/dotnet/src/webdriver/BiDi/Modules/Network/ResponseContent.cs new file mode 100644 index 0000000000000..21ee7f9376976 --- /dev/null +++ b/dotnet/src/webdriver/BiDi/Modules/Network/ResponseContent.cs @@ -0,0 +1,3 @@ +namespace OpenQA.Selenium.BiDi.Modules.Network; + +public record ResponseContent(long Size); diff --git a/dotnet/src/webdriver/BiDi/Modules/Network/ResponseData.cs b/dotnet/src/webdriver/BiDi/Modules/Network/ResponseData.cs new file mode 100644 index 0000000000000..9308535605dae --- /dev/null +++ b/dotnet/src/webdriver/BiDi/Modules/Network/ResponseData.cs @@ -0,0 +1,20 @@ +using System.Collections.Generic; +using System.Text.Json.Serialization; + +namespace OpenQA.Selenium.BiDi.Modules.Network; + +public record ResponseData(string Url, + string Protocol, + int Status, // TODO: should be unit + string StatusText, + bool FromCache, + IReadOnlyList
Headers, + string MymeType, + long BytesReceived, + long? HeadersSize, + long? BodySize, + ResponseContent Content) +{ + [JsonInclude] + public IReadOnlyList? AuthChallenges { get; internal set; } +} diff --git a/dotnet/src/webdriver/BiDi/Modules/Network/ResponseStartedEventArgs.cs b/dotnet/src/webdriver/BiDi/Modules/Network/ResponseStartedEventArgs.cs new file mode 100644 index 0000000000000..102525f39e9c1 --- /dev/null +++ b/dotnet/src/webdriver/BiDi/Modules/Network/ResponseStartedEventArgs.cs @@ -0,0 +1,7 @@ +using OpenQA.Selenium.BiDi.Modules.BrowsingContext; +using System; + +namespace OpenQA.Selenium.BiDi.Modules.Network; + +public record ResponseStartedEventArgs(BiDi BiDi, BrowsingContext.BrowsingContext Context, bool IsBlocked, Navigation Navigation, long RedirectCount, RequestData Request, DateTimeOffset Timestamp, ResponseData Response) + : BaseParametersEventArgs(BiDi, Context, IsBlocked, Navigation, RedirectCount, Request, Timestamp); diff --git a/dotnet/src/webdriver/BiDi/Modules/Network/SetCookieHeader.cs b/dotnet/src/webdriver/BiDi/Modules/Network/SetCookieHeader.cs new file mode 100644 index 0000000000000..cb20dbf8043af --- /dev/null +++ b/dotnet/src/webdriver/BiDi/Modules/Network/SetCookieHeader.cs @@ -0,0 +1,18 @@ +namespace OpenQA.Selenium.BiDi.Modules.Network; + +public record SetCookieHeader(string Name, BytesValue Value) +{ + public string? Domain { get; set; } + + public bool? HttpOnly { get; set; } + + public string? Expiry { get; set; } + + public long? MaxAge { get; set; } + + public string? Path { get; set; } + + public SameSite? SameSite { get; set; } + + public bool? Secure { get; set; } +} diff --git a/dotnet/src/webdriver/BiDi/Modules/Network/UrlPattern.cs b/dotnet/src/webdriver/BiDi/Modules/Network/UrlPattern.cs new file mode 100644 index 0000000000000..45ae7a5996576 --- /dev/null +++ b/dotnet/src/webdriver/BiDi/Modules/Network/UrlPattern.cs @@ -0,0 +1,31 @@ +using System.Text.Json.Serialization; + +namespace OpenQA.Selenium.BiDi.Modules.Network; + +[JsonPolymorphic(TypeDiscriminatorPropertyName = "type")] +[JsonDerivedType(typeof(UrlPatternPattern), "pattern")] +[JsonDerivedType(typeof(UrlPatternString), "string")] +public abstract record UrlPattern +{ + public static UrlPatternPattern Patter(string? protocol = null, string? hostname = null, string? port = null, string? pathname = null, string? search = null) + => new() { Protocol = protocol, Hostname = hostname, Port = port, Pathname = pathname, Search = search }; + + public static UrlPatternString String(string pattern) => new UrlPatternString(pattern); + + public static implicit operator UrlPattern(string value) => new UrlPatternString(value); +} + +public record UrlPatternPattern : UrlPattern +{ + public string? Protocol { get; set; } + + public string? Hostname { get; set; } + + public string? Port { get; set; } + + public string? Pathname { get; set; } + + public string? Search { get; set; } +} + +public record UrlPatternString(string Pattern) : UrlPattern; diff --git a/dotnet/src/webdriver/BiDi/Modules/Script/AddPreloadScriptCommand.cs b/dotnet/src/webdriver/BiDi/Modules/Script/AddPreloadScriptCommand.cs new file mode 100644 index 0000000000000..a4da0627ccba3 --- /dev/null +++ b/dotnet/src/webdriver/BiDi/Modules/Script/AddPreloadScriptCommand.cs @@ -0,0 +1,26 @@ +using OpenQA.Selenium.BiDi.Communication; +using System.Collections.Generic; + +namespace OpenQA.Selenium.BiDi.Modules.Script; + +internal class AddPreloadScriptCommand(AddPreloadScriptCommandParameters @params) : Command(@params); + +internal record AddPreloadScriptCommandParameters(string FunctionDeclaration) : CommandParameters +{ + public IEnumerable? Arguments { get; set; } + + public IEnumerable? Contexts { get; set; } + + public string? Sandbox { get; set; } +} + +public record AddPreloadScriptOptions : CommandOptions +{ + public IEnumerable? Arguments { get; set; } + + public IEnumerable? Contexts { get; set; } + + public string? Sandbox { get; set; } +} + +internal record AddPreloadScriptResult(PreloadScript Script); diff --git a/dotnet/src/webdriver/BiDi/Modules/Script/CallFunctionCommand.cs b/dotnet/src/webdriver/BiDi/Modules/Script/CallFunctionCommand.cs new file mode 100644 index 0000000000000..5de8a4b32ee5e --- /dev/null +++ b/dotnet/src/webdriver/BiDi/Modules/Script/CallFunctionCommand.cs @@ -0,0 +1,32 @@ +using OpenQA.Selenium.BiDi.Communication; +using System.Collections.Generic; + +namespace OpenQA.Selenium.BiDi.Modules.Script; + +internal class CallFunctionCommand(CallFunctionCommandParameters @params) : Command(@params); + +internal record CallFunctionCommandParameters(string FunctionDeclaration, bool AwaitPromise, Target Target) : CommandParameters +{ + public IEnumerable? Arguments { get; set; } + + public ResultOwnership? ResultOwnership { get; set; } + + public SerializationOptions? SerializationOptions { get; set; } + + public LocalValue? This { get; set; } + + public bool? UserActivation { get; set; } +} + +public record CallFunctionOptions : CommandOptions +{ + public IEnumerable? Arguments { get; set; } + + public ResultOwnership? ResultOwnership { get; set; } + + public SerializationOptions? SerializationOptions { get; set; } + + public object? This { get; set; } + + public bool? UserActivation { get; set; } +} \ No newline at end of file diff --git a/dotnet/src/webdriver/BiDi/Modules/Script/Channel.cs b/dotnet/src/webdriver/BiDi/Modules/Script/Channel.cs new file mode 100644 index 0000000000000..062692a3f9065 --- /dev/null +++ b/dotnet/src/webdriver/BiDi/Modules/Script/Channel.cs @@ -0,0 +1,14 @@ +namespace OpenQA.Selenium.BiDi.Modules.Script; + +public class Channel +{ + readonly BiDi _bidi; + + internal Channel(BiDi bidi, string id) + { + _bidi = bidi; + Id = id; + } + + internal string Id { get; } +} diff --git a/dotnet/src/webdriver/BiDi/Modules/Script/ChannelValue.cs b/dotnet/src/webdriver/BiDi/Modules/Script/ChannelValue.cs new file mode 100644 index 0000000000000..ade9e4b435bd0 --- /dev/null +++ b/dotnet/src/webdriver/BiDi/Modules/Script/ChannelValue.cs @@ -0,0 +1,13 @@ +namespace OpenQA.Selenium.BiDi.Modules.Script; + +public record ChannelValue : LocalValue +{ + public string Type => "channel"; +} + +public record ChannelProperties(Channel Channel) +{ + public SerializationOptions? SerializationOptions { get; set; } + + public ResultOwnership? Ownership { get; set; } +} \ No newline at end of file diff --git a/dotnet/src/webdriver/BiDi/Modules/Script/DisownCommand.cs b/dotnet/src/webdriver/BiDi/Modules/Script/DisownCommand.cs new file mode 100644 index 0000000000000..fa6ef150f7462 --- /dev/null +++ b/dotnet/src/webdriver/BiDi/Modules/Script/DisownCommand.cs @@ -0,0 +1,8 @@ +using OpenQA.Selenium.BiDi.Communication; +using System.Collections.Generic; + +namespace OpenQA.Selenium.BiDi.Modules.Script; + +internal class DisownCommand(DisownCommandParameters @params) : Command(@params); + +internal record DisownCommandParameters(IEnumerable Handles, Target Target) : CommandParameters; \ No newline at end of file diff --git a/dotnet/src/webdriver/BiDi/Modules/Script/EvaluateCommand.cs b/dotnet/src/webdriver/BiDi/Modules/Script/EvaluateCommand.cs new file mode 100644 index 0000000000000..dd557b84460d3 --- /dev/null +++ b/dotnet/src/webdriver/BiDi/Modules/Script/EvaluateCommand.cs @@ -0,0 +1,35 @@ +using OpenQA.Selenium.BiDi.Communication; + +namespace OpenQA.Selenium.BiDi.Modules.Script; + +internal class EvaluateCommand(EvaluateCommandParameters @params) : Command(@params); + +internal record EvaluateCommandParameters(string Expression, Target Target, bool AwaitPromise) : CommandParameters +{ + public ResultOwnership? ResultOwnership { get; set; } + + public SerializationOptions? SerializationOptions { get; set; } + + public bool? UserActivation { get; set; } +} + +public record EvaluateOptions : CommandOptions +{ + public ResultOwnership? ResultOwnership { get; set; } + + public SerializationOptions? SerializationOptions { get; set; } + + public bool? UserActivation { get; set; } +} + +// https://github.com/dotnet/runtime/issues/72604 +//[JsonPolymorphic(TypeDiscriminatorPropertyName = "type")] +//[JsonDerivedType(typeof(EvaluateResultSuccess), "success")] +//[JsonDerivedType(typeof(EvaluateResultException), "exception")] +public abstract record EvaluateResult; + +public record EvaluateResultSuccess(RemoteValue Result) : EvaluateResult; + +public record EvaluateResultException(ExceptionDetails ExceptionDetails) : EvaluateResult; + +public record ExceptionDetails(long ColumnNumber, long LineNumber, StackTrace StackTrace, string Text); diff --git a/dotnet/src/webdriver/BiDi/Modules/Script/GetRealmsCommand.cs b/dotnet/src/webdriver/BiDi/Modules/Script/GetRealmsCommand.cs new file mode 100644 index 0000000000000..c0139e11c86dd --- /dev/null +++ b/dotnet/src/webdriver/BiDi/Modules/Script/GetRealmsCommand.cs @@ -0,0 +1,22 @@ +using OpenQA.Selenium.BiDi.Communication; +using System.Collections.Generic; + +namespace OpenQA.Selenium.BiDi.Modules.Script; + +internal class GetRealmsCommand(GetRealmsCommandParameters @params) : Command(@params); + +internal record GetRealmsCommandParameters : CommandParameters +{ + public BrowsingContext.BrowsingContext? Context { get; set; } + + public RealmType? Type { get; set; } +} + +public record GetRealmsOptions : CommandOptions +{ + public BrowsingContext.BrowsingContext? Context { get; set; } + + public RealmType? Type { get; set; } +} + +internal record GetRealmsResult(IReadOnlyList Realms); diff --git a/dotnet/src/webdriver/BiDi/Modules/Script/Handle.cs b/dotnet/src/webdriver/BiDi/Modules/Script/Handle.cs new file mode 100644 index 0000000000000..b8200dd4836ae --- /dev/null +++ b/dotnet/src/webdriver/BiDi/Modules/Script/Handle.cs @@ -0,0 +1,14 @@ +namespace OpenQA.Selenium.BiDi.Modules.Script; + +public class Handle +{ + private readonly BiDi _bidi; + + public Handle(BiDi bidi, string id) + { + _bidi = bidi; + Id = id; + } + + public string Id { get; } +} diff --git a/dotnet/src/webdriver/BiDi/Modules/Script/InternalId.cs b/dotnet/src/webdriver/BiDi/Modules/Script/InternalId.cs new file mode 100644 index 0000000000000..a01dba2269aad --- /dev/null +++ b/dotnet/src/webdriver/BiDi/Modules/Script/InternalId.cs @@ -0,0 +1,14 @@ +namespace OpenQA.Selenium.BiDi.Modules.Script; + +public class InternalId +{ + readonly BiDi _bidi; + + public InternalId(BiDi bidi, string id) + { + _bidi = bidi; + Id = id; + } + + public string Id { get; } +} diff --git a/dotnet/src/webdriver/BiDi/Modules/Script/LocalValue.cs b/dotnet/src/webdriver/BiDi/Modules/Script/LocalValue.cs new file mode 100644 index 0000000000000..e8321dc25f328 --- /dev/null +++ b/dotnet/src/webdriver/BiDi/Modules/Script/LocalValue.cs @@ -0,0 +1,83 @@ +using System.Collections.Generic; +using System.Text.Json.Serialization; + +namespace OpenQA.Selenium.BiDi.Modules.Script; + +[JsonPolymorphic(TypeDiscriminatorPropertyName = "type")] +[JsonDerivedType(typeof(NumberLocalValue), "number")] +[JsonDerivedType(typeof(StringLocalValue), "string")] +[JsonDerivedType(typeof(NullLocalValue), "null")] +[JsonDerivedType(typeof(UndefinedLocalValue), "undefined")] +[JsonDerivedType(typeof(ArrayLocalValue), "array")] +[JsonDerivedType(typeof(DateLocalValue), "date")] +[JsonDerivedType(typeof(MapLocalValue), "map")] +[JsonDerivedType(typeof(ObjectLocalValue), "object")] +[JsonDerivedType(typeof(RegExpLocalValue), "regexp")] +[JsonDerivedType(typeof(SetLocalValue), "set")] +public abstract record LocalValue +{ + public static implicit operator LocalValue(int value) { return new NumberLocalValue(value); } + public static implicit operator LocalValue(string value) { return new StringLocalValue(value); } + + // TODO: Extend converting from types + public static LocalValue ConvertFrom(object? value) + { + switch (value) + { + case null: + return new NullLocalValue(); + case int: + return (int)value; + case string: + return (string)value; + case object: + { + var type = value.GetType(); + + var properties = type.GetProperties(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance); + + List> values = []; + + foreach (var property in properties) + { + values.Add([property.Name, ConvertFrom(property.GetValue(value))]); + } + + return new ObjectLocalValue(values); + } + } + } +} + +public abstract record PrimitiveProtocolLocalValue : LocalValue +{ + +} + +public record NumberLocalValue(long Value) : PrimitiveProtocolLocalValue +{ + public static explicit operator NumberLocalValue(int n) => new NumberLocalValue(n); +} + +public record StringLocalValue(string Value) : PrimitiveProtocolLocalValue; + +public record NullLocalValue : PrimitiveProtocolLocalValue; + +public record UndefinedLocalValue : PrimitiveProtocolLocalValue; + +public record ArrayLocalValue(IEnumerable Value) : LocalValue; + +public record DateLocalValue(string Value) : LocalValue; + +public record MapLocalValue(IDictionary Value) : LocalValue; // seems to implement IDictionary + +public record ObjectLocalValue(IEnumerable> Value) : LocalValue; + +public record RegExpLocalValue(RegExpValue Value) : LocalValue; + +public record RegExpValue(string Pattern) +{ + public string? Flags { get; set; } +} + +public record SetLocalValue(IEnumerable Value) : LocalValue; diff --git a/dotnet/src/webdriver/BiDi/Modules/Script/NodeProperties.cs b/dotnet/src/webdriver/BiDi/Modules/Script/NodeProperties.cs new file mode 100644 index 0000000000000..5cc390db0a3e5 --- /dev/null +++ b/dotnet/src/webdriver/BiDi/Modules/Script/NodeProperties.cs @@ -0,0 +1,28 @@ +using System.Collections.Generic; +using System.Text.Json.Serialization; + +namespace OpenQA.Selenium.BiDi.Modules.Script; + +public record NodeProperties(long NodeType, long ChildNodeCount) +{ + [JsonInclude] + public IReadOnlyDictionary? Attributes { get; internal set; } + + [JsonInclude] + public IReadOnlyList? Children { get; internal set; } + + [JsonInclude] + public string? LocalName { get; internal set; } + + [JsonInclude] + public Mode? Mode { get; internal set; } + + [JsonInclude] + public string? NamespaceUri { get; internal set; } + + [JsonInclude] + public string? NodeValue { get; internal set; } + + [JsonInclude] + public NodeRemoteValue? ShadowRoot { get; internal set; } +} diff --git a/dotnet/src/webdriver/BiDi/Modules/Script/PreloadScript.cs b/dotnet/src/webdriver/BiDi/Modules/Script/PreloadScript.cs new file mode 100644 index 0000000000000..1c82442c559a2 --- /dev/null +++ b/dotnet/src/webdriver/BiDi/Modules/Script/PreloadScript.cs @@ -0,0 +1,27 @@ +using System; +using System.Threading.Tasks; + +namespace OpenQA.Selenium.BiDi.Modules.Script; + +public class PreloadScript : IAsyncDisposable +{ + private readonly BiDi _bidi; + + public PreloadScript(BiDi bidi, string id) + { + _bidi = bidi; + Id = id; + } + + public string Id { get; } + + public Task RemoveAsync() + { + return _bidi.ScriptModule.RemovePreloadScriptAsync(this); + } + + public async ValueTask DisposeAsync() + { + await RemoveAsync().ConfigureAwait(false); + } +} diff --git a/dotnet/src/webdriver/BiDi/Modules/Script/Realm.cs b/dotnet/src/webdriver/BiDi/Modules/Script/Realm.cs new file mode 100644 index 0000000000000..c93ca95bba214 --- /dev/null +++ b/dotnet/src/webdriver/BiDi/Modules/Script/Realm.cs @@ -0,0 +1,14 @@ +namespace OpenQA.Selenium.BiDi.Modules.Script; + +public class Realm +{ + private readonly BiDi _bidi; + + public Realm(BiDi bidi, string id) + { + _bidi = bidi; + Id = id; + } + + public string Id { get; } +} diff --git a/dotnet/src/webdriver/BiDi/Modules/Script/RealmInfo.cs b/dotnet/src/webdriver/BiDi/Modules/Script/RealmInfo.cs new file mode 100644 index 0000000000000..c62c599b511d3 --- /dev/null +++ b/dotnet/src/webdriver/BiDi/Modules/Script/RealmInfo.cs @@ -0,0 +1,36 @@ +using System.Collections.Generic; + +namespace OpenQA.Selenium.BiDi.Modules.Script; + +// https://github.com/dotnet/runtime/issues/72604 +//[JsonPolymorphic(TypeDiscriminatorPropertyName = "type")] +//[JsonDerivedType(typeof(WindowRealmInfo), "window")] +//[JsonDerivedType(typeof(DedicatedWorkerRealmInfo), "dedicated-worker")] +//[JsonDerivedType(typeof(SharedWorkerRealmInfo), "shared-worker")] +//[JsonDerivedType(typeof(ServiceWorkerRealmInfo), "service-worker")] +//[JsonDerivedType(typeof(WorkerRealmInfo), "worker")] +//[JsonDerivedType(typeof(PaintWorkletRealmInfo), "paint-worklet")] +//[JsonDerivedType(typeof(AudioWorkletRealmInfo), "audio-worklet")] +//[JsonDerivedType(typeof(WorkletRealmInfo), "worklet")] +public abstract record RealmInfo(BiDi BiDi) : EventArgs(BiDi); + +public abstract record BaseRealmInfo(BiDi BiDi, Realm Realm, string Origin) : RealmInfo(BiDi); + +public record WindowRealmInfo(BiDi BiDi, Realm Realm, string Origin, BrowsingContext.BrowsingContext Context) : BaseRealmInfo(BiDi, Realm, Origin) +{ + public string? Sandbox { get; set; } +} + +public record DedicatedWorkerRealmInfo(BiDi BiDi, Realm Realm, string Origin, IReadOnlyList Owners) : BaseRealmInfo(BiDi, Realm, Origin); + +public record SharedWorkerRealmInfo(BiDi BiDi, Realm Realm, string Origin) : BaseRealmInfo(BiDi, Realm, Origin); + +public record ServiceWorkerRealmInfo(BiDi BiDi, Realm Realm, string Origin) : BaseRealmInfo(BiDi, Realm, Origin); + +public record WorkerRealmInfo(BiDi BiDi, Realm Realm, string Origin) : BaseRealmInfo(BiDi, Realm, Origin); + +public record PaintWorkletRealmInfo(BiDi BiDi, Realm Realm, string Origin) : BaseRealmInfo(BiDi, Realm, Origin); + +public record AudioWorkletRealmInfo(BiDi BiDi, Realm Realm, string Origin) : BaseRealmInfo(BiDi, Realm, Origin); + +public record WorkletRealmInfo(BiDi BiDi, Realm Realm, string Origin) : BaseRealmInfo(BiDi, Realm, Origin); diff --git a/dotnet/src/webdriver/BiDi/Modules/Script/RealmType.cs b/dotnet/src/webdriver/BiDi/Modules/Script/RealmType.cs new file mode 100644 index 0000000000000..018b9ad514a9e --- /dev/null +++ b/dotnet/src/webdriver/BiDi/Modules/Script/RealmType.cs @@ -0,0 +1,13 @@ +namespace OpenQA.Selenium.BiDi.Modules.Script; + +public enum RealmType +{ + Window, + DedicatedWorker, + SharedWorker, + ServiceWorker, + Worker, + PaintWorker, + AudioWorker, + Worklet +} diff --git a/dotnet/src/webdriver/BiDi/Modules/Script/RemoteReference.cs b/dotnet/src/webdriver/BiDi/Modules/Script/RemoteReference.cs new file mode 100644 index 0000000000000..55023b19127bb --- /dev/null +++ b/dotnet/src/webdriver/BiDi/Modules/Script/RemoteReference.cs @@ -0,0 +1,13 @@ +namespace OpenQA.Selenium.BiDi.Modules.Script; + +public abstract record RemoteReference : LocalValue; + +public record SharedReference(string SharedId) : RemoteReference +{ + public Handle? Handle { get; set; } +} + +public record RemoteObjectReference(Handle Handle) : RemoteReference +{ + public string? SharedId { get; set; } +} diff --git a/dotnet/src/webdriver/BiDi/Modules/Script/RemoteValue.cs b/dotnet/src/webdriver/BiDi/Modules/Script/RemoteValue.cs new file mode 100644 index 0000000000000..437bf430ca68e --- /dev/null +++ b/dotnet/src/webdriver/BiDi/Modules/Script/RemoteValue.cs @@ -0,0 +1,243 @@ +using System; +using System.Collections.Generic; +using System.Text.Json.Serialization; + +namespace OpenQA.Selenium.BiDi.Modules.Script; + +// https://github.com/dotnet/runtime/issues/72604 +//[JsonPolymorphic(TypeDiscriminatorPropertyName = "type")] +//[JsonDerivedType(typeof(NumberRemoteValue), "number")] +//[JsonDerivedType(typeof(StringRemoteValue), "string")] +//[JsonDerivedType(typeof(NullRemoteValue), "null")] +//[JsonDerivedType(typeof(UndefinedRemoteValue), "undefined")] +//[JsonDerivedType(typeof(SymbolRemoteValue), "symbol")] +//[JsonDerivedType(typeof(ObjectRemoteValue), "object")] +//[JsonDerivedType(typeof(FunctionRemoteValue), "function")] +//[JsonDerivedType(typeof(RegExpRemoteValue), "regexp")] +//[JsonDerivedType(typeof(DateRemoteValue), "date")] +//[JsonDerivedType(typeof(MapRemoteValue), "map")] +//[JsonDerivedType(typeof(SetRemoteValue), "set")] +//[JsonDerivedType(typeof(WeakMapRemoteValue), "weakmap")] +//[JsonDerivedType(typeof(WeakSetRemoteValue), "weakset")] +//[JsonDerivedType(typeof(GeneratorRemoteValue), "generator")] +//[JsonDerivedType(typeof(ErrorRemoteValue), "error")] +//[JsonDerivedType(typeof(ProxyRemoteValue), "proxy")] +//[JsonDerivedType(typeof(PromiseRemoteValue), "promise")] +//[JsonDerivedType(typeof(TypedArrayRemoteValue), "typedarray")] +//[JsonDerivedType(typeof(ArrayBufferRemoteValue), "arraybuffer")] +//[JsonDerivedType(typeof(NodeListRemoteValue), "nodelist")] +//[JsonDerivedType(typeof(HtmlCollectionRemoteValue), "htmlcollection")] +//[JsonDerivedType(typeof(NodeRemoteValue), "node")] +//[JsonDerivedType(typeof(WindowProxyRemoteValue), "window")] +public abstract record RemoteValue +{ + public static implicit operator int(RemoteValue remoteValue) => (int)((NumberRemoteValue)remoteValue).Value; + public static implicit operator long(RemoteValue remoteValue) => ((NumberRemoteValue)remoteValue).Value; + public static implicit operator string(RemoteValue remoteValue) + { + return remoteValue switch + { + StringRemoteValue stringValue => stringValue.Value, + NullRemoteValue => null!, + _ => throw new BiDiException($"Cannot convert {remoteValue} to string") + }; + } + + // TODO: extend types + public TResult? ConvertTo() + { + var type = typeof(TResult); + + if (type == typeof(int)) + { + return (TResult)(Convert.ToInt32(((NumberRemoteValue)this).Value) as object); + } + else if (type == typeof(string)) + { + return (TResult)(((StringRemoteValue)this).Value as object); + } + else if (type is object) + { + // :) + return (TResult)new object(); + } + + throw new BiDiException("Cannot convert ....."); + } +} + +public abstract record PrimitiveProtocolRemoteValue : RemoteValue; + +public record NumberRemoteValue(long Value) : PrimitiveProtocolRemoteValue; + +public record StringRemoteValue(string Value) : PrimitiveProtocolRemoteValue; + +public record NullRemoteValue : PrimitiveProtocolRemoteValue; + +public record UndefinedRemoteValue : PrimitiveProtocolRemoteValue; + +public record SymbolRemoteValue : RemoteValue +{ + public Handle? Handle { get; set; } + + public InternalId? InternalId { get; set; } +} + +public record ArrayRemoteValue : RemoteValue +{ + public Handle? Handle { get; set; } + + public InternalId? InternalId { get; set; } + + public IReadOnlyList? Value { get; set; } +} + +public record ObjectRemoteValue : RemoteValue +{ + public Handle? Handle { get; set; } + + public InternalId? InternalId { get; set; } + + public IReadOnlyList>? Value { get; set; } +} + +public record FunctionRemoteValue : RemoteValue +{ + public Handle? Handle { get; set; } + + public InternalId? InternalId { get; set; } +} + +public record RegExpRemoteValue(RegExpValue Value) : RemoteValue +{ + public Handle? Handle { get; set; } + + public InternalId? InternalId { get; set; } +} + +public record DateRemoteValue(string Value) : RemoteValue +{ + public Handle? Handle { get; set; } + + public InternalId? InternalId { get; set; } +} + +public record MapRemoteValue : RemoteValue +{ + public Handle? Handle { get; set; } + + public InternalId? InternalId { get; set; } + + public IDictionary? Value { get; set; } +} + +public record SetRemoteValue : RemoteValue +{ + public Handle? Handle { get; set; } + + public InternalId? InternalId { get; set; } + + public IReadOnlyList? Value { get; set; } +} + +public record WeakMapRemoteValue : RemoteValue +{ + public Handle? Handle { get; set; } + + public InternalId? InternalId { get; set; } +} + +public record WeakSetRemoteValue : RemoteValue +{ + public Handle? Handle { get; set; } + + public InternalId? InternalId { get; set; } +} + +public record GeneratorRemoteValue : RemoteValue +{ + public Handle? Handle { get; set; } + + public InternalId? InternalId { get; set; } +} + +public record ErrorRemoteValue : RemoteValue +{ + public Handle? Handle { get; set; } + + public InternalId? InternalId { get; set; } +} + +public record ProxyRemoteValue : RemoteValue +{ + public Handle? Handle { get; set; } + + public InternalId? InternalId { get; set; } +} + +public record PromiseRemoteValue : RemoteValue +{ + public Handle? Handle { get; set; } + + public InternalId? InternalId { get; set; } +} + +public record TypedArrayRemoteValue : RemoteValue +{ + public Handle? Handle { get; set; } + + public InternalId? InternalId { get; set; } +} + +public record ArrayBufferRemoteValue : RemoteValue +{ + public Handle? Handle { get; set; } + + public InternalId? InternalId { get; set; } +} + +public record NodeListRemoteValue : RemoteValue +{ + public Handle? Handle { get; set; } + + public InternalId? InternalId { get; set; } + + public IReadOnlyList? Value { get; set; } +} + +public record HtmlCollectionRemoteValue : RemoteValue +{ + public Handle? Handle { get; set; } + + public InternalId? InternalId { get; set; } + + public IReadOnlyList? Value { get; set; } +} + +public record NodeRemoteValue : RemoteValue +{ + [JsonInclude] + public string? SharedId { get; internal set; } + + public Handle? Handle { get; set; } + + public InternalId? InternalId { get; set; } + + [JsonInclude] + public NodeProperties? Value { get; internal set; } +} + +public record WindowProxyRemoteValue(WindowProxyProperties Value) : RemoteValue +{ + public Handle? Handle { get; set; } + + public InternalId? InternalId { get; set; } +} + +public record WindowProxyProperties(BrowsingContext.BrowsingContext Context); + +public enum Mode +{ + Open, + Closed +} diff --git a/dotnet/src/webdriver/BiDi/Modules/Script/RemovePreloadScriptCommand.cs b/dotnet/src/webdriver/BiDi/Modules/Script/RemovePreloadScriptCommand.cs new file mode 100644 index 0000000000000..355eec3f9bd75 --- /dev/null +++ b/dotnet/src/webdriver/BiDi/Modules/Script/RemovePreloadScriptCommand.cs @@ -0,0 +1,9 @@ +using OpenQA.Selenium.BiDi.Communication; + +namespace OpenQA.Selenium.BiDi.Modules.Script; + +internal class RemovePreloadScriptCommand(RemovePreloadScriptCommandParameters @params) : Command(@params); + +internal record RemovePreloadScriptCommandParameters(PreloadScript Script) : CommandParameters; + +public record RemovePreloadScriptOptions : CommandOptions; diff --git a/dotnet/src/webdriver/BiDi/Modules/Script/ResultOwnership.cs b/dotnet/src/webdriver/BiDi/Modules/Script/ResultOwnership.cs new file mode 100644 index 0000000000000..4d844b0cc5362 --- /dev/null +++ b/dotnet/src/webdriver/BiDi/Modules/Script/ResultOwnership.cs @@ -0,0 +1,7 @@ +namespace OpenQA.Selenium.BiDi.Modules.Script; + +public enum ResultOwnership +{ + Root, + None +} diff --git a/dotnet/src/webdriver/BiDi/Modules/Script/ScriptEvaluateException.cs b/dotnet/src/webdriver/BiDi/Modules/Script/ScriptEvaluateException.cs new file mode 100644 index 0000000000000..fd4703c0e3d4b --- /dev/null +++ b/dotnet/src/webdriver/BiDi/Modules/Script/ScriptEvaluateException.cs @@ -0,0 +1,14 @@ +using System; + +namespace OpenQA.Selenium.BiDi.Modules.Script; + +public class ScriptEvaluateException(EvaluateResultException evaluateResultException) : Exception +{ + private readonly EvaluateResultException _evaluateResultException = evaluateResultException; + + public string Text => _evaluateResultException.ExceptionDetails.Text; + + public long ColumNumber => _evaluateResultException.ExceptionDetails.ColumnNumber; + + public override string Message => $"{Text}{Environment.NewLine}{_evaluateResultException.ExceptionDetails.StackTrace}"; +} diff --git a/dotnet/src/webdriver/BiDi/Modules/Script/ScriptModule.cs b/dotnet/src/webdriver/BiDi/Modules/Script/ScriptModule.cs new file mode 100644 index 0000000000000..e2b86a6cadc54 --- /dev/null +++ b/dotnet/src/webdriver/BiDi/Modules/Script/ScriptModule.cs @@ -0,0 +1,91 @@ +using OpenQA.Selenium.BiDi.Communication; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace OpenQA.Selenium.BiDi.Modules.Script; + +public sealed class ScriptModule(Broker broker) : Module(broker) +{ + public async Task EvaluateAsync(string expression, bool awaitPromise, Target target, EvaluateOptions? options = null) + { + var @params = new EvaluateCommandParameters(expression, target, awaitPromise); + + if (options is not null) + { + @params.ResultOwnership = options.ResultOwnership; + @params.SerializationOptions = options.SerializationOptions; + @params.UserActivation = options.UserActivation; + } + + var result = await Broker.ExecuteCommandAsync(new EvaluateCommand(@params), options).ConfigureAwait(false); + + if (result is EvaluateResultException exp) + { + throw new ScriptEvaluateException(exp); + } + + return ((EvaluateResultSuccess)result).Result; + } + + public async Task CallFunctionAsync(string functionDeclaration, bool awaitPromise, Target target, CallFunctionOptions? options = null) + { + var @params = new CallFunctionCommandParameters(functionDeclaration, awaitPromise, target); + + if (options is not null) + { + @params.Arguments = options.Arguments?.Select(LocalValue.ConvertFrom); + @params.ResultOwnership = options.ResultOwnership; + @params.SerializationOptions = options.SerializationOptions; + @params.This = LocalValue.ConvertFrom(options.This); + @params.UserActivation = options.UserActivation; + } + + var result = await Broker.ExecuteCommandAsync(new CallFunctionCommand(@params), options).ConfigureAwait(false); + + if (result is EvaluateResultException exp) + { + throw new ScriptEvaluateException(exp); + } + + return ((EvaluateResultSuccess)result).Result; + } + + public async Task> GetRealmsAsync(GetRealmsOptions? options = null) + { + var @params = new GetRealmsCommandParameters(); + + if (options is not null) + { + @params.Context = options.Context; + @params.Type = options.Type; + } + + var result = await Broker.ExecuteCommandAsync(new GetRealmsCommand(@params), options).ConfigureAwait(false); + + return result.Realms; + } + + public async Task AddPreloadScriptAsync(string functionDeclaration, AddPreloadScriptOptions? options = null) + { + var @params = new AddPreloadScriptCommandParameters(functionDeclaration); + + if (options is not null) + { + @params.Contexts = options.Contexts; + @params.Arguments = options.Arguments; + @params.Sandbox = options.Sandbox; + } + + var result = await Broker.ExecuteCommandAsync(new AddPreloadScriptCommand(@params), options).ConfigureAwait(false); + + return result.Script; + } + + public async Task RemovePreloadScriptAsync(PreloadScript script, RemovePreloadScriptOptions? options = null) + { + var @params = new RemovePreloadScriptCommandParameters(script); + + await Broker.ExecuteCommandAsync(new RemovePreloadScriptCommand(@params), options).ConfigureAwait(false); + } +} diff --git a/dotnet/src/webdriver/BiDi/Modules/Script/SerializationOptions.cs b/dotnet/src/webdriver/BiDi/Modules/Script/SerializationOptions.cs new file mode 100644 index 0000000000000..f46e3812e649f --- /dev/null +++ b/dotnet/src/webdriver/BiDi/Modules/Script/SerializationOptions.cs @@ -0,0 +1,17 @@ +namespace OpenQA.Selenium.BiDi.Modules.Script; + +public class SerializationOptions +{ + public long? MaxDomDepth { get; set; } + + public long? MaxObjectDepth { get; set; } + + public ShadowTree? IncludeShadowTree { get; set; } +} + +public enum ShadowTree +{ + None, + Open, + All +} \ No newline at end of file diff --git a/dotnet/src/webdriver/BiDi/Modules/Script/Source.cs b/dotnet/src/webdriver/BiDi/Modules/Script/Source.cs new file mode 100644 index 0000000000000..e4f24fd53f7b1 --- /dev/null +++ b/dotnet/src/webdriver/BiDi/Modules/Script/Source.cs @@ -0,0 +1,6 @@ +namespace OpenQA.Selenium.BiDi.Modules.Script; + +public record Source(Realm Realm) +{ + public BrowsingContext.BrowsingContext? Context { get; set; } +} diff --git a/dotnet/src/webdriver/BiDi/Modules/Script/StackFrame.cs b/dotnet/src/webdriver/BiDi/Modules/Script/StackFrame.cs new file mode 100644 index 0000000000000..ab117b318a276 --- /dev/null +++ b/dotnet/src/webdriver/BiDi/Modules/Script/StackFrame.cs @@ -0,0 +1,3 @@ +namespace OpenQA.Selenium.BiDi.Modules.Script; + +public record StackFrame(long LineNumber, long ColumnNumber, string Url, string FunctionName); diff --git a/dotnet/src/webdriver/BiDi/Modules/Script/StackTrace.cs b/dotnet/src/webdriver/BiDi/Modules/Script/StackTrace.cs new file mode 100644 index 0000000000000..16b25def230c6 --- /dev/null +++ b/dotnet/src/webdriver/BiDi/Modules/Script/StackTrace.cs @@ -0,0 +1,5 @@ +using System.Collections.Generic; + +namespace OpenQA.Selenium.BiDi.Modules.Script; + +public record StackTrace(IReadOnlyCollection CallFrames); diff --git a/dotnet/src/webdriver/BiDi/Modules/Script/Target.cs b/dotnet/src/webdriver/BiDi/Modules/Script/Target.cs new file mode 100644 index 0000000000000..9a5569ed99779 --- /dev/null +++ b/dotnet/src/webdriver/BiDi/Modules/Script/Target.cs @@ -0,0 +1,19 @@ +using System.Text.Json.Serialization; + +namespace OpenQA.Selenium.BiDi.Modules.Script; + +[JsonDerivedType(typeof(RealmTarget))] +[JsonDerivedType(typeof(ContextTarget))] +public abstract record Target; + +public record RealmTarget(Realm Realm) : Target; + +public record ContextTarget(BrowsingContext.BrowsingContext Context) : Target +{ + public string? Sandbox { get; set; } +} + +public class ContextTargetOptions +{ + public string? Sandbox { set; get; } +} diff --git a/dotnet/src/webdriver/BiDi/Modules/Session/CapabilitiesRequest.cs b/dotnet/src/webdriver/BiDi/Modules/Session/CapabilitiesRequest.cs new file mode 100644 index 0000000000000..41b35a987592b --- /dev/null +++ b/dotnet/src/webdriver/BiDi/Modules/Session/CapabilitiesRequest.cs @@ -0,0 +1,10 @@ +using System.Collections.Generic; + +namespace OpenQA.Selenium.BiDi.Modules.Session; + +public class CapabilitiesRequest +{ + public CapabilityRequest? AlwaysMatch { get; set; } + + public IEnumerable? FirstMatch { get; set; } +} diff --git a/dotnet/src/webdriver/BiDi/Modules/Session/CapabilityRequest.cs b/dotnet/src/webdriver/BiDi/Modules/Session/CapabilityRequest.cs new file mode 100644 index 0000000000000..c33f0a1e2d3b6 --- /dev/null +++ b/dotnet/src/webdriver/BiDi/Modules/Session/CapabilityRequest.cs @@ -0,0 +1,16 @@ +namespace OpenQA.Selenium.BiDi.Modules.Session; + +public class CapabilityRequest +{ + public bool? AcceptInsecureCerts { get; set; } + + public string? BrowserName { get; set; } + + public string? BrowserVersion { get; set; } + + public string? PlatformName { get; set; } + + public ProxyConfiguration? ProxyConfiguration { get; set; } + + public bool? WebSocketUrl { get; set; } +} diff --git a/dotnet/src/webdriver/BiDi/Modules/Session/EndCommand.cs b/dotnet/src/webdriver/BiDi/Modules/Session/EndCommand.cs new file mode 100644 index 0000000000000..9390467e52ef1 --- /dev/null +++ b/dotnet/src/webdriver/BiDi/Modules/Session/EndCommand.cs @@ -0,0 +1,7 @@ +using OpenQA.Selenium.BiDi.Communication; + +namespace OpenQA.Selenium.BiDi.Modules.Session; + +internal class EndCommand() : Command(CommandParameters.Empty); + +public record EndOptions : CommandOptions; diff --git a/dotnet/src/webdriver/BiDi/Modules/Session/NewCommand.cs b/dotnet/src/webdriver/BiDi/Modules/Session/NewCommand.cs new file mode 100644 index 0000000000000..176378b678ee5 --- /dev/null +++ b/dotnet/src/webdriver/BiDi/Modules/Session/NewCommand.cs @@ -0,0 +1,18 @@ +using OpenQA.Selenium.BiDi.Communication; + +namespace OpenQA.Selenium.BiDi.Modules.Session; + +internal class NewCommand(NewCommandParameters @params) : Command(@params); + +internal record NewCommandParameters(CapabilitiesRequest Capabilities) : CommandParameters; + +public record NewOptions : CommandOptions; + +public record NewResult(string SessionId, Capability Capability); + +public record Capability(bool AcceptInsecureCerts, string BrowserName, string BrowserVersion, string PlatformName, bool SetWindowRect, string UserAgent) +{ + public ProxyConfiguration? Proxy { get; set; } + + public string? WebSocketUrl { get; set; } +} diff --git a/dotnet/src/webdriver/BiDi/Modules/Session/ProxyConfiguration.cs b/dotnet/src/webdriver/BiDi/Modules/Session/ProxyConfiguration.cs new file mode 100644 index 0000000000000..b039b940eb593 --- /dev/null +++ b/dotnet/src/webdriver/BiDi/Modules/Session/ProxyConfiguration.cs @@ -0,0 +1,32 @@ +using System.Text.Json.Serialization; + +namespace OpenQA.Selenium.BiDi.Modules.Session; + +[JsonPolymorphic(TypeDiscriminatorPropertyName = "proxyType")] +[JsonDerivedType(typeof(AutodetectProxyConfiguration), "autodetect")] +[JsonDerivedType(typeof(DirectProxyConfiguration), "direct")] +[JsonDerivedType(typeof(ManualProxyConfiguration), "manual")] +[JsonDerivedType(typeof(PacProxyConfiguration), "pac")] +[JsonDerivedType(typeof(SystemProxyConfiguration), "system")] +public abstract record ProxyConfiguration; + +public record AutodetectProxyConfiguration : ProxyConfiguration; + +public record DirectProxyConfiguration : ProxyConfiguration; + +public record ManualProxyConfiguration : ProxyConfiguration +{ + public string? FtpProxy { get; set; } + + public string? HttpProxy { get; set; } + + public string? SslProxy { get; set; } + + public string? SocksProxy { get; set; } + + public long? SocksVersion { get; set; } +} + +public record PacProxyConfiguration(string ProxyAutoconfigUrl) : ProxyConfiguration; + +public record SystemProxyConfiguration : ProxyConfiguration; diff --git a/dotnet/src/webdriver/BiDi/Modules/Session/SessionModule.cs b/dotnet/src/webdriver/BiDi/Modules/Session/SessionModule.cs new file mode 100644 index 0000000000000..1a2e4b7ea033e --- /dev/null +++ b/dotnet/src/webdriver/BiDi/Modules/Session/SessionModule.cs @@ -0,0 +1,49 @@ +using OpenQA.Selenium.BiDi.Communication; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace OpenQA.Selenium.BiDi.Modules.Session; + +internal sealed class SessionModule(Broker broker) : Module(broker) +{ + public async Task StatusAsync(StatusOptions? options = null) + { + return await Broker.ExecuteCommandAsync(new StatusCommand(), options).ConfigureAwait(false); + } + + public async Task SubscribeAsync(IEnumerable events, SubscribeOptions? options = null) + { + var @params = new SubscribeCommandParameters(events); + + if (options is not null) + { + @params.Contexts = options.Contexts; + } + + await Broker.ExecuteCommandAsync(new SubscribeCommand(@params), options).ConfigureAwait(false); + } + + public async Task UnsubscribeAsync(IEnumerable events, UnsubscribeOptions? options = null) + { + var @params = new SubscribeCommandParameters(events); + + if (options is not null) + { + @params.Contexts = options.Contexts; + } + + await Broker.ExecuteCommandAsync(new UnsubscribeCommand(@params), options).ConfigureAwait(false); + } + + public async Task NewAsync(CapabilitiesRequest capabilitiesRequest, NewOptions? options = null) + { + var @params = new NewCommandParameters(capabilitiesRequest); + + return await Broker.ExecuteCommandAsync(new NewCommand(@params), options).ConfigureAwait(false); + } + + public async Task EndAsync(EndOptions? options = null) + { + await Broker.ExecuteCommandAsync(new EndCommand(), options).ConfigureAwait(false); + } +} diff --git a/dotnet/src/webdriver/BiDi/Modules/Session/StatusCommand.cs b/dotnet/src/webdriver/BiDi/Modules/Session/StatusCommand.cs new file mode 100644 index 0000000000000..147750c330ad8 --- /dev/null +++ b/dotnet/src/webdriver/BiDi/Modules/Session/StatusCommand.cs @@ -0,0 +1,9 @@ +using OpenQA.Selenium.BiDi.Communication; + +namespace OpenQA.Selenium.BiDi.Modules.Session; + +internal class StatusCommand() : Command(CommandParameters.Empty); + +public record StatusResult(bool Ready, string Message); + +public record StatusOptions : CommandOptions; diff --git a/dotnet/src/webdriver/BiDi/Modules/Session/SubscribeCommand.cs b/dotnet/src/webdriver/BiDi/Modules/Session/SubscribeCommand.cs new file mode 100644 index 0000000000000..5d28945b348f8 --- /dev/null +++ b/dotnet/src/webdriver/BiDi/Modules/Session/SubscribeCommand.cs @@ -0,0 +1,16 @@ +using OpenQA.Selenium.BiDi.Communication; +using System.Collections.Generic; + +namespace OpenQA.Selenium.BiDi.Modules.Session; + +internal class SubscribeCommand(SubscribeCommandParameters @params) : Command(@params); + +internal record SubscribeCommandParameters(IEnumerable Events) : CommandParameters +{ + public IEnumerable? Contexts { get; set; } +} + +public record SubscribeOptions : CommandOptions +{ + public IEnumerable? Contexts { get; set; } +} diff --git a/dotnet/src/webdriver/BiDi/Modules/Session/UnsubscribeCommand.cs b/dotnet/src/webdriver/BiDi/Modules/Session/UnsubscribeCommand.cs new file mode 100644 index 0000000000000..fd2de3a771fe4 --- /dev/null +++ b/dotnet/src/webdriver/BiDi/Modules/Session/UnsubscribeCommand.cs @@ -0,0 +1,7 @@ +using OpenQA.Selenium.BiDi.Communication; + +namespace OpenQA.Selenium.BiDi.Modules.Session; + +internal class UnsubscribeCommand(SubscribeCommandParameters @params) : Command(@params); + +public record UnsubscribeOptions : SubscribeOptions; diff --git a/dotnet/src/webdriver/BiDi/Modules/Storage/DeleteCookiesCommand.cs b/dotnet/src/webdriver/BiDi/Modules/Storage/DeleteCookiesCommand.cs new file mode 100644 index 0000000000000..3e3729454f371 --- /dev/null +++ b/dotnet/src/webdriver/BiDi/Modules/Storage/DeleteCookiesCommand.cs @@ -0,0 +1,16 @@ +using OpenQA.Selenium.BiDi.Communication; + +namespace OpenQA.Selenium.BiDi.Modules.Storage; + +internal class DeleteCookiesCommand(DeleteCookiesCommandParameters @params) : Command(@params); + +internal record DeleteCookiesCommandParameters : CommandParameters +{ + public CookieFilter? Filter { get; set; } + + public PartitionDescriptor? Partition { get; set; } +} + +public record DeleteCookiesOptions : GetCookiesOptions; + +public record DeleteCookiesResult(PartitionKey PartitionKey); diff --git a/dotnet/src/webdriver/BiDi/Modules/Storage/GetCookiesCommand.cs b/dotnet/src/webdriver/BiDi/Modules/Storage/GetCookiesCommand.cs new file mode 100644 index 0000000000000..baf769afc2829 --- /dev/null +++ b/dotnet/src/webdriver/BiDi/Modules/Storage/GetCookiesCommand.cs @@ -0,0 +1,59 @@ +using OpenQA.Selenium.BiDi.Communication; +using System; +using System.Collections.Generic; +using System.Text.Json.Serialization; + +namespace OpenQA.Selenium.BiDi.Modules.Storage; + +internal class GetCookiesCommand(GetCookiesCommandParameters @params) : Command(@params); + +internal record GetCookiesCommandParameters : CommandParameters +{ + public CookieFilter? Filter { get; set; } + + public PartitionDescriptor? Partition { get; set; } +} + +public record GetCookiesOptions : CommandOptions +{ + public CookieFilter? Filter { get; set; } + + public PartitionDescriptor? Partition { get; set; } +} + +public record GetCookiesResult(IReadOnlyList Cookies, PartitionKey PartitionKey); + +public class CookieFilter +{ + public string? Name { get; set; } + + public Network.BytesValue? Value { get; set; } + + public string? Domain { get; set; } + + public string? Path { get; set; } + + public long? Size { get; set; } + + public bool? HttpOnly { get; set; } + + public bool? Secure { get; set; } + + public Network.SameSite? SameSite { get; set; } + + public DateTimeOffset? Expiry { get; set; } +} + +[JsonPolymorphic(TypeDiscriminatorPropertyName = "type")] +[JsonDerivedType(typeof(BrowsingContextPartitionDescriptor), "context")] +[JsonDerivedType(typeof(StorageKeyPartitionDescriptor), "storageKey")] +public abstract record PartitionDescriptor; + +public record BrowsingContextPartitionDescriptor(BrowsingContext.BrowsingContext Context) : PartitionDescriptor; + +public record StorageKeyPartitionDescriptor : PartitionDescriptor +{ + public string? UserContext { get; set; } + + public string? SourceOrigin { get; set; } +} diff --git a/dotnet/src/webdriver/BiDi/Modules/Storage/PartitionKey.cs b/dotnet/src/webdriver/BiDi/Modules/Storage/PartitionKey.cs new file mode 100644 index 0000000000000..df01ba28a2b71 --- /dev/null +++ b/dotnet/src/webdriver/BiDi/Modules/Storage/PartitionKey.cs @@ -0,0 +1,8 @@ +namespace OpenQA.Selenium.BiDi.Modules.Storage; + +public class PartitionKey +{ + public string? UserContext { get; set; } + + public string? SourceOrigin { get; set; } +} diff --git a/dotnet/src/webdriver/BiDi/Modules/Storage/SetCookieCommand.cs b/dotnet/src/webdriver/BiDi/Modules/Storage/SetCookieCommand.cs new file mode 100644 index 0000000000000..916b9d1d3cd35 --- /dev/null +++ b/dotnet/src/webdriver/BiDi/Modules/Storage/SetCookieCommand.cs @@ -0,0 +1,31 @@ +using OpenQA.Selenium.BiDi.Communication; +using System; + +namespace OpenQA.Selenium.BiDi.Modules.Storage; + +internal class SetCookieCommand(SetCookieCommandParameters @params) : Command(@params); + +internal record SetCookieCommandParameters(PartialCookie Cookie) : CommandParameters +{ + public PartitionDescriptor? Partition { get; set; } +} + +public record PartialCookie(string Name, Network.BytesValue Value, string Domain) +{ + public string? Path { get; set; } + + public bool? HttpOnly { get; set; } + + public bool? Secure { get; set; } + + public Network.SameSite? SameSite { get; set; } + + public DateTimeOffset? Expiry { get; set; } +} + +public record SetCookieOptions : CommandOptions +{ + public PartitionDescriptor? Partition { get; set; } +} + +public record SetCookieResult(PartitionKey PartitionKey); diff --git a/dotnet/src/webdriver/BiDi/Modules/Storage/StorageModule.cs b/dotnet/src/webdriver/BiDi/Modules/Storage/StorageModule.cs new file mode 100644 index 0000000000000..36f5817ce6f42 --- /dev/null +++ b/dotnet/src/webdriver/BiDi/Modules/Storage/StorageModule.cs @@ -0,0 +1,45 @@ +using OpenQA.Selenium.BiDi.Communication; +using System.Threading.Tasks; + +namespace OpenQA.Selenium.BiDi.Modules.Storage; + +public class StorageModule(Broker broker) : Module(broker) +{ + public async Task GetCookiesAsync(GetCookiesOptions? options = null) + { + var @params = new GetCookiesCommandParameters(); + + if (options is not null) + { + @params.Filter = options.Filter; + @params.Partition = options.Partition; + } + + return await Broker.ExecuteCommandAsync(new GetCookiesCommand(@params), options).ConfigureAwait(false); + } + + public async Task DeleteCookiesAsync(DeleteCookiesOptions? options = null) + { + var @params = new DeleteCookiesCommandParameters(); + + if (options is not null) + { + @params.Filter = options.Filter; + @params.Partition = options.Partition; + } + + return await Broker.ExecuteCommandAsync(new DeleteCookiesCommand(@params), options).ConfigureAwait(false); + } + + public async Task SetCookieAsync(PartialCookie cookie, SetCookieOptions? options = null) + { + var @params = new SetCookieCommandParameters(cookie); + + if (options is not null) + { + @params.Partition = options.Partition; + } + + return await Broker.ExecuteCommandAsync(new SetCookieCommand(@params), options).ConfigureAwait(false); + } +} diff --git a/dotnet/src/webdriver/BiDi/Properties/IsExternalInit.cs b/dotnet/src/webdriver/BiDi/Properties/IsExternalInit.cs new file mode 100644 index 0000000000000..274e9de445240 --- /dev/null +++ b/dotnet/src/webdriver/BiDi/Properties/IsExternalInit.cs @@ -0,0 +1,3 @@ +namespace System.Runtime.CompilerServices; + +internal static class IsExternalInit; diff --git a/dotnet/src/webdriver/BiDi/Subscription.cs b/dotnet/src/webdriver/BiDi/Subscription.cs new file mode 100644 index 0000000000000..6b3a68de477fa --- /dev/null +++ b/dotnet/src/webdriver/BiDi/Subscription.cs @@ -0,0 +1,43 @@ +using OpenQA.Selenium.BiDi.Communication; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace OpenQA.Selenium.BiDi; + +public class Subscription : IAsyncDisposable +{ + private readonly Broker Broker; + private readonly Communication.EventHandler _eventHandler; + + internal Subscription(Broker broker, Communication.EventHandler eventHandler) + { + Broker = broker; + _eventHandler = eventHandler; + } + + public async Task UnsubscribeAsync() + { + await Broker.UnsubscribeAsync(_eventHandler).ConfigureAwait(false); + } + + public async ValueTask DisposeAsync() + { + await UnsubscribeAsync().ConfigureAwait(false); + } +} + +public class SubscriptionOptions +{ + public TimeSpan? Timeout { get; set; } +} + +public class BrowsingContextsSubscriptionOptions : SubscriptionOptions +{ + public BrowsingContextsSubscriptionOptions(SubscriptionOptions? options) + { + Timeout = options?.Timeout; + } + + public IEnumerable? Contexts { get; set; } +} diff --git a/dotnet/src/webdriver/BiDi/WebDriver.Extensions.cs b/dotnet/src/webdriver/BiDi/WebDriver.Extensions.cs new file mode 100644 index 0000000000000..199f12cc4a401 --- /dev/null +++ b/dotnet/src/webdriver/BiDi/WebDriver.Extensions.cs @@ -0,0 +1,27 @@ +using OpenQA.Selenium.BiDi.Modules.BrowsingContext; +using System.Threading.Tasks; + +namespace OpenQA.Selenium.BiDi; + +public static class WebDriverExtensions +{ + public static async Task AsBidirectionalAsync(this IWebDriver webDriver) + { + var webSocketUrl = ((IHasCapabilities)webDriver).Capabilities.GetCapability("webSocketUrl"); + + if (webSocketUrl is null) throw new System.Exception("The driver is not compatible with bidirectional protocol."); + + var bidi = await BiDi.ConnectAsync(webSocketUrl.ToString()!).ConfigureAwait(false); + + return bidi; + } + + public static async Task AsBidirectionalContextAsync(this IWebDriver webDriver) + { + var bidi = await webDriver.AsBidirectionalAsync(); + + var currentBrowsingContext = new BrowsingContext(bidi, webDriver.CurrentWindowHandle); + + return currentBrowsingContext; + } +}