Skip to content

[dotnet] Use namespace file scoped #15651

New issue

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

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

Already on GitHub? Sign in to your account

Merged

Conversation

nvborisenko
Copy link
Member

@nvborisenko nvborisenko commented Apr 20, 2025

User description

Simplify reading of .net code.

🔗 Related Issues

💥 What does this PR do?

I created .editorconfig file especially allocated for dotnet rules.

🔧 Implementation Notes

💡 Additional Considerations

It is massive change, now or later?

🔄 Types of changes

  • Cleanup (formatting, renaming)
  • Bug fix (backwards compatible)
  • New feature (non-breaking change which adds functionality and tests!)
  • Breaking change (fix or feature that would cause existing functionality to change)

PR Type

Enhancement


Description

  • Refactored all .NET source and test files to use file-scoped namespaces, modernizing code style and improving readability.

  • Flattened class and interface structures by removing unnecessary nesting and indentation, resulting in cleaner and more maintainable code.

  • No functional or logic changes; all updates are structural and formatting only.

  • Introduced a dedicated .editorconfig file to enforce consistent .NET code style rules across the project.


Changes walkthrough 📝

Relevant files
Formatting
9 files
WebDriver.cs
Refactor to file-scoped namespace for WebDriver class       

dotnet/src/webdriver/WebDriver.cs

  • Converted the namespace declaration to use file-scoped syntax.
  • Refactored the entire class to use file-scoped namespace, removing an
    extra indentation level.
  • No functional code changes, only formatting and structural update for
    improved readability.
  • +879/-880
    DriverOptions.cs
    Refactor to file-scoped namespace for DriverOptions           

    dotnet/src/webdriver/DriverOptions.cs

  • Changed the namespace declaration to file-scoped syntax.
  • Refactored the file to remove an indentation level, updating all code
    to align with file-scoped namespace.
  • No logic or functional changes, only formatting and structural
    improvements.
  • +450/-451
    SafariSpecificTests.cs
    Refactor SafariSpecificTests to file-scoped namespace       

    dotnet/test/safari/SafariSpecificTests.cs

  • Updated the namespace declaration to use file-scoped syntax.
  • Removed an unnecessary indentation level from the test class.
  • No changes to test logic or functionality.
  • +4/-5     
    ElementFindingTest.cs
    Refactor to file-scoped namespace and flatten test class structure

    dotnet/test/common/ElementFindingTest.cs

  • Converted the namespace declaration to use file-scoped syntax.
  • Removed an extra level of class nesting, making ElementFindingTest a
    top-level class in the file.
  • Reformatted all test methods to be direct members of the file-scoped
    namespace, removing indentation and braces associated with the
    previous nested class structure.
  • No functional changes to test logic; all changes are structural and
    formatting.
  • +838/-839
    RemoteWebDriver.cs
    Refactor to file-scoped namespace and reformat RemoteWebDriver

    dotnet/src/webdriver/Remote/RemoteWebDriver.cs

  • Changed the namespace declaration to file-scoped syntax.
  • Reformatted the entire class to remove one level of indentation, as a
    result of the file-scoped namespace.
  • No changes to logic or functionality; all changes are structural and
    formatting.
  • +508/-509
    DriverStartingEventArgs.cs
    Refactor to file-scoped namespace and flatten DriverStartingEventArgs
    class

    dotnet/test/common/Environment/DriverStartingEventArgs.cs

  • Changed the namespace declaration to file-scoped syntax.
  • Removed an extra level of class nesting, making
    DriverStartingEventArgs a top-level class in the file.
  • Reformatted property and constructor definitions to match the new
    structure.
  • No changes to logic or functionality; all changes are structural and
    formatting.
  • +12/-13 
    ExecutingJavascriptTest.cs
    Refactor to file-scoped namespace and flatten class structure in test
    file

    dotnet/test/common/ExecutingJavascriptTest.cs

  • Converted the namespace declaration to use file-scoped syntax.
  • Removed an extra level of class nesting, flattening the class
    structure.
  • Reformatted all test methods and code blocks to match the new
    file-scoped namespace style.
  • No logic changes, only structural and formatting updates for modern C#
    style.
  • +739/-740
    Actions.cs
    Refactor Actions class to file-scoped namespace and flatten structure

    dotnet/src/webdriver/Interactions/Actions.cs

  • Changed the namespace declaration to use file-scoped syntax.
  • Removed an extra level of class nesting, flattening the class
    structure.
  • Reformatted the entire file to match the file-scoped namespace style.
  • No logic changes, only structural and formatting updates for modern C#
    style.
  • +540/-541
    ILoadableComponent.cs
    Refactor ILoadableComponent to file-scoped namespace and flatten
    interface

    dotnet/src/support/UI/ILoadableComponent.cs

  • Changed the namespace declaration to use file-scoped syntax.
  • Flattened the interface definition by removing unnecessary braces and
    indentation.
  • Reformatted XML documentation comments to match the new structure.
  • No logic changes, only formatting and structure updates.
  • +23/-24 
    Additional files
    101 files
    .editorconfig +2/-0     
    EventFiringWebDriver.cs +1468/-1469
    FindElementEventArgs.cs +38/-39 
    GetShadowRootEventArgs.cs +22/-23 
    WebDriverExceptionEventArgs.cs +22/-23 
    WebDriverNavigationEventArgs.cs +31/-32 
    WebDriverScriptEventArgs.cs +22/-23 
    WebElementEventArgs.cs +22/-23 
    WebElementValueEventArgs.cs +19/-20 
    WebDriverExtensions.cs +94/-95 
    LoadableComponentException.cs +31/-32 
    LoadableComponent{T}.cs +96/-97 
    PopupWindowFinder.cs +101/-102
    SelectElement.cs +321/-322
    SlowLoadableComponent{T}.cs +92/-93 
    UnexpectedTagNameException.cs +41/-42 
    Alert.cs +48/-49 
    By.cs +317/-318
    CapabilityType.cs +176/-177
    ChromeDriver.cs +146/-147
    ChromeDriverService.cs +55/-56 
    ChromeOptions.cs +62/-63 
    ChromiumAndroidOptions.cs +18/-19 
    ChromiumDriver.cs +397/-398
    ChromiumDriverService.cs +157/-158
    ChromiumMobileEmulationDeviceSettings.cs +44/-45 
    ChromiumNetworkConditions.cs +86/-87 
    ChromiumOptions.cs +531/-532
    ChromiumPerformanceLoggingPreferences.cs +70/-71 
    Command.cs +127/-128
    CommandInfo.cs +71/-72 
    CommandInfoRepository.cs +102/-103
    Cookie.cs +293/-294
    CookieJar.cs +83/-84 
    DefaultFileDetector.cs +13/-14 
    DetachedShadowRootException.cs +31/-32 
    AuthRequiredEventArgs.cs +21/-22 
    BindingCalledEventArgs.cs +27/-28 
    CommandResponseException.cs +32/-33 
    CommandResponseExtensions.cs +13/-14 
    CommandResponseTypeMap.cs +42/-43 
    ConsoleApiArgument.cs +22/-23 
    ConsoleApiCalledEventArgs.cs +28/-29 
    DevToolsCommandData.cs +66/-67 
    DevToolsDomains.cs +96/-97 
    DevToolsEventData.cs +22/-23 
    DevToolsExtensionMethods.cs +60/-61 
    DevToolsOptions.cs +14/-15 
    DevToolsSession.cs +507/-508
    DevToolsSessionDomains.cs +18/-19 
    DevToolsSessionEventReceivedEventArgs.cs +27/-28 
    DevToolsSessionLogMessageEventArgs.cs +34/-35 
    DevToolsVersionInfo.cs +96/-97 
    EntryAddedEventArgs.cs +16/-17 
    ExceptionThrownEventArgs.cs +21/-22 
    ICommand.cs +19/-20 
    IDevTools.cs +39/-40 
    IDevToolsSession.cs +66/-67 
    JavaScript.cs +107/-108
    JsonEnumMemberConverter.cs +38/-39 
    Log.cs +31/-32 
    LogEntry.cs +21/-22 
    Network.cs +173/-174
    RequestPausedEventArgs.cs +21/-22 
    ResponsePausedEventArgs.cs +16/-17 
    Target.cs +63/-64 
    TargetAttachedEventArgs.cs +27/-28 
    TargetDetachedEventArgs.cs +21/-22 
    TargetInfo.cs +51/-52 
    WebSocketConnection.cs +223/-224
    WebSocketConnectionDataReceivedEventArgs.cs +16/-17 
    V133Domains.cs +38/-39 
    V133JavaScript.cs +139/-140
    V133Log.cs +46/-47 
    V133Network.cs +277/-278
    V133Target.cs +117/-118
    V134Domains.cs +38/-39 
    V134JavaScript.cs +139/-140
    V134Log.cs +46/-47 
    V134Network.cs +277/-278
    V134Target.cs +117/-118
    V135Domains.cs +38/-39 
    V135JavaScript.cs +139/-140
    V135Log.cs +46/-47 
    V135Network.cs +277/-278
    V135Target.cs +117/-118
    DomMutatedEventArgs.cs +13/-14 
    DomMutationData.cs +40/-41 
    DriverCommand.cs +466/-467
    DriverFinder.cs +114/-115
    DriverOptionsMergeResult.cs +12/-13 
    DriverProcessStartedEventArgs.cs +36/-37 
    DriverProcessStartingEventArgs.cs +18/-19 
    DriverService.cs +284/-285
    DriverServiceNotFoundException.cs +31/-32 
    EdgeDriver.cs +116/-117
    EdgeDriverService.cs +64/-65 
    EdgeOptions.cs +66/-67 
    ElementClickInterceptedException.cs +31/-32 
    ElementCoordinates.cs +36/-37 
    Additional files not shown

    Need help?
  • Type /help how to ... in the comments thread for any questions about Qodo Merge usage.
  • Check out the documentation for more information.
  • @selenium-ci selenium-ci added C-dotnet .NET Bindings B-support Issue or PR related to support classes labels Apr 20, 2025
    @selenium-ci
    Copy link
    Member

    Thank you, @nvborisenko for this code suggestion.

    The support packages contain example code that many users find helpful, but they do not necessarily represent
    the best practices for using Selenium, and the Selenium team is not currently merging changes to them.

    We actively encourage people to add the wrapper and helper code that makes sense for them to their own frameworks.
    If you have any questions, please contact us

    Copy link
    Contributor

    qodo-merge-pro bot commented Apr 20, 2025

    PR Reviewer Guide 🔍

    (Review updated until commit 866f81c)

    Here are some key observations to aid the review process:

    🎫 Ticket compliance analysis ❌

    1234 - Not compliant

    Non-compliant requirements:

    • Fix issue where JavaScript in link's href attribute is not triggered on click() in Firefox 42.0
    • Ensure compatibility between Selenium 2.48.x and Firefox 42.0
    • Make JavaScript execution in href attributes work consistently between Selenium versions

    5678 - Not compliant

    Non-compliant requirements:

    • Fix "Error: ConnectFailure (Connection refused)" when instantiating ChromeDriver after the first instance
    • Ensure multiple ChromeDriver instances can be created without connection errors
    • Address issue specific to Chrome 65.0.3325.181 with ChromeDriver 2.35 on Ubuntu 16.04.4

    ⏱️ Estimated effort to review: 2 🔵🔵⚪⚪⚪
    🧪 No relevant tests
    🔒 No security concerns identified
    ⚡ Recommended focus areas for review

    Code Readability

    The file-scoped namespace change significantly improves readability but should be verified for consistent indentation and formatting throughout the large file to ensure maintainability.

    namespace OpenQA.Selenium;
    
    /// <summary>
    /// A base class representing a driver for a web browser.
    /// </summary>
    public class WebDriver : IWebDriver, ISearchContext, IJavaScriptExecutor, IFindsElement, ITakesScreenshot, ISupportsPrint, IActionExecutor, IAllowsFileDetection, IHasCapabilities, IHasCommandExecutor, IHasSessionId, ICustomDriverCommandExecutor, IHasVirtualAuthenticator
    {
        /// <summary>
        /// The default command timeout for HTTP requests in a RemoteWebDriver instance.
        /// </summary>
        protected static readonly TimeSpan DefaultCommandTimeout = TimeSpan.FromSeconds(60);
        private IFileDetector fileDetector = new DefaultFileDetector();
        private NetworkManager network;
        private WebElementFactory elementFactory;
    
        private readonly List<string> registeredCommands = new List<string>();
    
        /// <summary>
        /// Initializes a new instance of the <see cref="WebDriver"/> class.
        /// </summary>
        /// <param name="executor">The <see cref="ICommandExecutor"/> object used to execute commands.</param>
        /// <param name="capabilities">The <see cref="ICapabilities"/> object used to configure the driver session.</param>
        protected WebDriver(ICommandExecutor executor, ICapabilities capabilities)
        {
            this.CommandExecutor = executor;
    
            try
            {
                this.StartSession(capabilities);
            }
            catch (Exception)
            {
                try
                {
                    // Failed to start driver session, disposing of driver
                    this.Quit();
                }
                catch
                {
                    // Ignore the clean-up exception. We'll propagate the original failure.
                }
                throw;
            }
    
            this.elementFactory = new WebElementFactory(this);
            this.registeredCommands.AddRange(DriverCommand.KnownCommands);
    
            if (this is ISupportsLogs)
            {
                // Only add the legacy log commands if the driver supports
                // retrieving the logs via the extension end points.
                this.RegisterDriverCommand(DriverCommand.GetAvailableLogTypes, new HttpCommandInfo(HttpCommandInfo.GetCommand, "/session/{sessionId}/se/log/types"), true);
                this.RegisterDriverCommand(DriverCommand.GetLog, new HttpCommandInfo(HttpCommandInfo.PostCommand, "/session/{sessionId}/se/log"), true);
            }
        }
    
        /// <summary>
        /// Gets the <see cref="ICommandExecutor"/> which executes commands for this driver.
        /// </summary>
        public ICommandExecutor CommandExecutor { get; }
    
        /// <summary>
        /// Gets the <see cref="ICapabilities"/> that the driver session was created with, which may be different from those requested.
        /// </summary>
        public ICapabilities Capabilities { get; private set; }
    
        /// <summary>
        /// Gets or sets the URL the browser is currently displaying.
        /// </summary>
        /// <seealso cref="IWebDriver.Url"/>
        /// <seealso cref="INavigation.GoToUrl(string)"/>
        /// <seealso cref="INavigation.GoToUrl(System.Uri)"/>
        public string Url
        {
            get
            {
                Response commandResponse = this.Execute(DriverCommand.GetCurrentUrl, null);
    
                commandResponse.EnsureValueIsNotNull();
                return commandResponse.Value.ToString()!;
            }
    
            set => new Navigator(this).GoToUrl(value);
        }
    
        /// <summary>
        /// Gets the title of the current browser window.
        /// </summary>
        public string Title
        {
            get
            {
                Response commandResponse = this.Execute(DriverCommand.GetTitle, null);
    
                return commandResponse.Value?.ToString() ?? string.Empty;
            }
        }
    
        /// <summary>
        /// Gets the source of the page last loaded by the browser.
        /// </summary>
        public string PageSource
        {
            get
            {
                Response commandResponse = this.Execute(DriverCommand.GetPageSource, null);
    
                commandResponse.EnsureValueIsNotNull();
                return commandResponse.Value.ToString()!;
            }
        }
    
        /// <summary>
        /// Gets the current window handle, which is an opaque handle to this
        /// window that uniquely identifies it within this driver instance.
        /// </summary>
        public string CurrentWindowHandle
        {
            get
            {
                Response commandResponse = this.Execute(DriverCommand.GetCurrentWindowHandle, null);
    
                commandResponse.EnsureValueIsNotNull();
                return commandResponse.Value.ToString()!;
            }
        }
    
        /// <summary>
        /// Gets the window handles of open browser windows.
        /// </summary>
        public ReadOnlyCollection<string> WindowHandles
        {
            get
            {
                Response commandResponse = this.Execute(DriverCommand.GetWindowHandles, null);
    
                commandResponse.EnsureValueIsNotNull();
                object?[] handles = (object?[])commandResponse.Value;
                List<string> handleList = new List<string>(handles.Length);
                foreach (object? handle in handles)
                {
                    handleList.Add(handle!.ToString()!);
                }
    
                return handleList.AsReadOnly();
            }
        }
    
        /// <summary>
        /// Gets a value indicating whether this object is a valid action executor.
        /// </summary>
        public bool IsActionExecutor => true;
    
        /// <summary>
        /// Gets the <see cref="Selenium.SessionId"/> for the current session of this driver.
        /// </summary>
        public SessionId SessionId { get; private set; }
    
        /// <summary>
        /// Gets or sets the <see cref="IFileDetector"/> responsible for detecting
        /// sequences of keystrokes representing file paths and names.
        /// </summary>
        /// <exception cref="ArgumentNullException">If value is set to <see langword="null"/>.</exception>
        public virtual IFileDetector FileDetector
        {
            get => this.fileDetector;
            set => this.fileDetector = value ?? throw new ArgumentNullException(nameof(value), "FileDetector cannot be null");
        }
    
        internal INetwork Network => this.network ??= new NetworkManager(this);
    
        /// <summary>
        /// Gets or sets the factory object used to create instances of <see cref="WebElement"/>
        /// or its subclasses.
        /// </summary>
        /// <exception cref="ArgumentNullException">If value is set to <see langword="null"/>.</exception>
        protected WebElementFactory ElementFactory
        {
            get => this.elementFactory;
            set => this.elementFactory = value ?? throw new ArgumentNullException(nameof(value));
        }
    
        /// <summary>
        /// Closes the Browser
        /// </summary>
        public void Close()
        {
            this.Execute(DriverCommand.Close, null);
        }
    
        /// <summary>
        /// Dispose the WebDriver Instance
        /// </summary>
        public void Dispose()
        {
            this.Dispose(true);
            GC.SuppressFinalize(this);
        }
    
        /// <summary>
        /// Executes JavaScript "asynchronously" in the context of the currently selected frame or window,
        /// executing the callback function specified as the last argument in the list of arguments.
        /// </summary>
        /// <param name="script">The JavaScript code to execute.</param>
        /// <param name="args">The arguments to the script.</param>
        /// <returns>The value returned by the script.</returns>
        public object? ExecuteAsyncScript(string script, params object?[]? args)
        {
            return this.ExecuteScriptCommand(script, DriverCommand.ExecuteAsyncScript, args);
        }
    
        /// <summary>
        /// Executes JavaScript in the context of the currently selected frame or window
        /// </summary>
        /// <param name="script">The JavaScript code to execute.</param>
        /// <param name="args">The arguments to the script.</param>
        /// <returns>The value returned by the script.</returns>
        public object? ExecuteScript(string script, params object?[]? args)
        {
            return this.ExecuteScriptCommand(script, DriverCommand.ExecuteScript, args);
        }
    
        /// <summary>
        /// Executes JavaScript in the context of the currently selected frame or window
        /// </summary>
        /// <param name="script">A <see cref="PinnedScript"/> object containing the JavaScript code to execute.</param>
        /// <param name="args">The arguments to the script.</param>
        /// <returns>The value returned by the script.</returns>
        /// <exception cref="ArgumentNullException">If <paramref name="script" /> is <see langword="null"/>.</exception>
        public object? ExecuteScript(PinnedScript script, params object?[]? args)
        {
            if (script == null)
            {
                throw new ArgumentNullException(nameof(script));
            }
    
            return this.ExecuteScript(script.MakeExecutionScript(), args);
        }
    
        /// <summary>
        /// Finds the first element in the page that matches the <see cref="By"/> object
        /// </summary>
        /// <param name="by">By mechanism to find the object</param>
        /// <returns>IWebElement object so that you can interact with that object</returns>
        /// <exception cref="ArgumentNullException">If <paramref name="by" /> is <see langword="null"/>.</exception>
        /// <example>
        /// <code>
        /// IWebDriver driver = new InternetExplorerDriver();
        /// IWebElement elem = driver.FindElement(By.Name("q"));
        /// </code>
        /// </example>
        public IWebElement FindElement(By by)
        {
            if (by == null)
            {
                throw new ArgumentNullException(nameof(@by), "by cannot be null");
            }
    
            return by.FindElement(this);
        }
    
        /// <summary>
        /// Finds an element matching the given mechanism and value.
        /// </summary>
        /// <param name="mechanism">The mechanism by which to find the element.</param>
        /// <param name="value">The value to use to search for the element.</param>
        /// <returns>The first <see cref="IWebElement"/> matching the given criteria.</returns>
        public virtual IWebElement FindElement(string mechanism, string value)
        {
            Dictionary<string, object> parameters = new Dictionary<string, object>();
            parameters.Add("using", mechanism);
            parameters.Add("value", value);
    
            Response commandResponse = this.Execute(DriverCommand.FindElement, parameters);
    
            return this.GetElementFromResponse(commandResponse)!;
        }
    
        /// <summary>
        /// Finds the elements on the page by using the <see cref="By"/> object and returns a ReadOnlyCollection of the Elements on the page
        /// </summary>
        /// <param name="by">By mechanism to find the element</param>
        /// <returns>ReadOnlyCollection of IWebElement</returns>
        /// <example>
        /// <code>
        /// IWebDriver driver = new InternetExplorerDriver();
        /// ReadOnlyCollection<![CDATA[<IWebElement>]]> classList = driver.FindElements(By.ClassName("class"));
        /// </code>
        /// </example>
        public ReadOnlyCollection<IWebElement> FindElements(By by)
        {
            if (by == null)
            {
                throw new ArgumentNullException(nameof(@by), "by cannot be null");
            }
    
            return by.FindElements(this);
        }
    
        /// <summary>
        /// Finds all elements matching the given mechanism and value.
        /// </summary>
        /// <param name="mechanism">The mechanism by which to find the elements.</param>
        /// <param name="value">The value to use to search for the elements.</param>
        /// <returns>A collection of all of the <see cref="IWebElement">IWebElements</see> matching the given criteria.</returns>
        public virtual ReadOnlyCollection<IWebElement> FindElements(string mechanism, string value)
        {
            Dictionary<string, object> parameters = new Dictionary<string, object>();
            parameters.Add("using", mechanism);
            parameters.Add("value", value);
    
            Response commandResponse = this.Execute(DriverCommand.FindElements, parameters);
    
            return this.GetElementsFromResponse(commandResponse);
        }
    
        /// <summary>
        /// Gets a <see cref="Screenshot"/> object representing the image of the page on the screen.
        /// </summary>
        /// <returns>A <see cref="Screenshot"/> object containing the image.</returns>
        public Screenshot GetScreenshot()
        {
            Response screenshotResponse = this.Execute(DriverCommand.Screenshot, null);
    
            screenshotResponse.EnsureValueIsNotNull();
            string base64 = screenshotResponse.Value.ToString()!;
            return new Screenshot(base64);
        }
    
        /// <summary>
        /// Gets a <see cref="PrintDocument"/> object representing a PDF-formatted print representation of the page.
        /// </summary>
        /// <param name="printOptions">A <see cref="PrintOptions"/> object describing the options of the printed document.</param>
        /// <returns>The <see cref="PrintDocument"/> object containing the PDF-formatted print representation of the page.</returns>
        /// <exception cref="ArgumentNullException">If <paramref name="printOptions"/> is <see langword="null"/>.</exception>
        public PrintDocument Print(PrintOptions printOptions)
        {
            if (printOptions is null)
            {
                throw new ArgumentNullException(nameof(printOptions));
            }
    
            Response commandResponse = this.Execute(DriverCommand.Print, printOptions.ToDictionary());
    
            commandResponse.EnsureValueIsNotNull();
            string base64 = commandResponse.Value.ToString()!;
            return new PrintDocument(base64);
        }
    
        /// <summary>
        /// Performs the specified list of actions with this action executor.
        /// </summary>
        /// <param name="actionSequenceList">The list of action sequences to perform.</param>
        public void PerformActions(IList<ActionSequence> actionSequenceList)
        {
            if (actionSequenceList == null)
            {
                throw new ArgumentNullException(nameof(actionSequenceList), "List of action sequences must not be null");
            }
    
            List<object> objectList = new List<object>();
            foreach (ActionSequence sequence in actionSequenceList)
            {
                objectList.Add(sequence.ToDictionary());
            }
    
            Dictionary<string, object> parameters = new Dictionary<string, object>();
            parameters["actions"] = objectList;
    
            this.Execute(DriverCommand.Actions, parameters);
        }
    
        /// <summary>
        /// Resets the input state of the action executor.
        /// </summary>
        public void ResetInputState()
        {
            this.Execute(DriverCommand.CancelActions, null);
        }
    
        /// <summary>
        /// Close the Browser and Dispose of WebDriver
        /// </summary>
        public void Quit()
        {
            this.Dispose();
        }
    
        /// <summary>
        /// Method to give you access to switch frames and windows
        /// </summary>
        /// <returns>Returns an Object that allows you to Switch Frames and Windows</returns>
        /// <example>
        /// <code>
        /// IWebDriver driver = new InternetExplorerDriver();
        /// driver.SwitchTo().Frame("FrameName");
        /// </code>
        /// </example>
        public ITargetLocator SwitchTo()
        {
            return new TargetLocator(this);
        }
    
        /// <summary>
        /// Instructs the driver to change its settings.
        /// </summary>
        /// <returns>An <see cref="IOptions"/> object allowing the user to change
        /// the settings of the driver.</returns>
        public IOptions Manage()
        {
            return new OptionsManager(this);
        }
    
        /// <summary>
        /// Instructs the driver to navigate the browser to another location.
        /// </summary>
        /// <returns>An <see cref="INavigation"/> object allowing the user to access
        /// the browser's history and to navigate to a given URL.</returns>
        public INavigation Navigate()
        {
            return new Navigator(this);
        }
    
        /// <summary>
        /// Executes a command with this driver.
        /// </summary>
        /// <param name="driverCommandToExecute">The name of the command to execute. The command name must be registered with the command executor, and must not be a command name known to this driver type.</param>
        /// <param name="parameters">A <see cref="Dictionary{K, V}"/> containing the names and values of the parameters of the command.</param>
        /// <returns>A <see cref="Response"/> containing information about the success or failure of the command and any data returned by the command.</returns>
        /// <exception cref="WebDriverException">The command returned an exceptional value.</exception>
        public object? ExecuteCustomDriverCommand(string driverCommandToExecute, Dictionary<string, object> parameters)
        {
            if (this.registeredCommands.Contains(driverCommandToExecute))
            {
                throw new WebDriverException(string.Format(CultureInfo.InvariantCulture, "A command named '{0}' is predefined by the driver class and cannot be executed with ExecuteCustomDriverCommand. It should be executed using a named method instead.", driverCommandToExecute));
            }
    
            return this.Execute(driverCommandToExecute, parameters).Value;
        }
    
        /// <summary>
        /// Registers a set of commands to be executed with this driver instance.
        /// </summary>
        /// <param name="commands">An <see cref="IReadOnlyDictionary{String, CommandInfo}"/> where the keys are the names of the commands to register, and the values are the <see cref="CommandInfo"/> objects describing the commands.</param>
        public void RegisterCustomDriverCommands(IReadOnlyDictionary<string, CommandInfo> commands)
        {
            foreach (KeyValuePair<string, CommandInfo> entry in commands)
            {
                this.RegisterCustomDriverCommand(entry.Key, entry.Value);
            }
        }
    
        /// <summary>
        /// Registers a command to be executed with this driver instance.
        /// </summary>
        /// <param name="commandName">The unique name of the command to register.</param>
        /// <param name="commandInfo">The <see cref="CommandInfo"/> object describing the command.</param>
        /// <returns><see langword="true"/> if the command was registered; otherwise, <see langword="false"/>.</returns>
        public bool RegisterCustomDriverCommand(string commandName, [NotNullWhen(true)] CommandInfo? commandInfo)
        {
            return this.RegisterDriverCommand(commandName, commandInfo, false);
        }
    
        /// <summary>
        /// Registers a command to be executed with this driver instance.
        /// </summary>
        /// <param name="commandName">The unique name of the command to register.</param>
        /// <param name="commandInfo">The <see cref="CommandInfo"/> object describing the command.</param>
        /// <param name="isInternalCommand"><see langword="true"/> if the registered command is internal to the driver; otherwise <see langword="false"/>.</param>
        /// <returns><see langword="true"/> if the command was registered; otherwise, <see langword="false"/>.</returns>
        internal bool RegisterDriverCommand(string commandName, [NotNullWhen(true)] CommandInfo? commandInfo, bool isInternalCommand)
        {
            if (this.CommandExecutor.TryAddCommand(commandName, commandInfo))
            {
                if (isInternalCommand)
                {
                    this.registeredCommands.Add(commandName);
                }
    
                return true;
            }
    
            return false;
        }
    
        /// <summary>
        /// Find the element in the response
        /// </summary>
        /// <param name="response">Response from the browser</param>
        /// <returns>Element from the page, or <see langword="null"/> if the response does not contain a dictionary.</returns>
        internal IWebElement? GetElementFromResponse(Response response)
        {
            if (response.Value is Dictionary<string, object?> elementDictionary)
            {
                return this.elementFactory.CreateElement(elementDictionary);
            }
    
            return null;
        }
    
        /// <summary>
        /// Finds the elements that are in the response
        /// </summary>
        /// <param name="response">Response from the browser</param>
        /// <returns>Collection of elements</returns>
        internal ReadOnlyCollection<IWebElement> GetElementsFromResponse(Response response)
        {
            List<IWebElement> toReturn = new List<IWebElement>();
            if (response.Value is object?[] elements)
            {
                foreach (object? elementObject in elements)
                {
                    if (elementObject is Dictionary<string, object?> elementDictionary)
                    {
                        WebElement element = this.elementFactory.CreateElement(elementDictionary);
                        toReturn.Add(element);
                    }
                }
            }
    
            return toReturn.AsReadOnly();
        }
    
        /// <summary>
        /// Executes a command with this driver.
        /// </summary>
        /// <param name="driverCommandToExecute">A <see cref="DriverCommand"/> value representing the command to execute.</param>
        /// <param name="parameters">A <see cref="Dictionary{K, V}"/> containing the names and values of the parameters of the command.</param>
        /// <returns>A <see cref="Response"/> containing information about the success or failure of the command and any data returned by the command.</returns>
        /// <exception cref="ArgumentNullException">If <paramref name="driverCommandToExecute"/> is <see langword="null"/>.</exception>
        protected internal virtual Response Execute(string driverCommandToExecute, Dictionary<string,
    #nullable disable
            object
    #nullable enable
            >? parameters)
        {
            return Task.Run(() => this.ExecuteAsync(driverCommandToExecute, parameters)).GetAwaiter().GetResult();
        }
    
        /// <summary>
        /// Executes a command with this driver.
        /// </summary>
        /// <param name="driverCommandToExecute">A <see cref="DriverCommand"/> value representing the command to execute.</param>
        /// <param name="parameters">A <see cref="Dictionary{K, V}"/> containing the names and values of the parameters of the command.</param>
        /// <returns>A <see cref="Response"/> containing information about the success or failure of the command and any data returned by the command.</returns>
        /// <exception cref="ArgumentNullException">If <paramref name="driverCommandToExecute"/> is <see langword="null"/>.</exception>
        protected internal virtual async Task<Response> ExecuteAsync(string driverCommandToExecute, Dictionary<string,
    #nullable disable
            object
    #nullable enable
            >? parameters)
        {
            Command commandToExecute = new Command(SessionId, driverCommandToExecute, parameters);
    
            Response commandResponse = await this.CommandExecutor.ExecuteAsync(commandToExecute).ConfigureAwait(false);
    
            if (commandResponse.Status != WebDriverResult.Success)
            {
                UnpackAndThrowOnError(commandResponse, driverCommandToExecute);
            }
    
            return commandResponse;
        }
    
        /// <summary>
        /// Starts a session with the driver
        /// </summary>
        /// <param name="capabilities">Capabilities of the browser</param>
        [MemberNotNull(nameof(SessionId))]
        [MemberNotNull(nameof(Capabilities))]
        protected void StartSession(ICapabilities capabilities)
        {
            Dictionary<string, object> parameters = new Dictionary<string, object>();
    
            // If the object passed into the RemoteWebDriver constructor is a
            // RemoteSessionSettings object, it is expected that all intermediate
            // and end nodes are compliant with the W3C WebDriver Specification,
            // and therefore will already contain all of the appropriate values
            // for establishing a session.
            if (capabilities is not RemoteSessionSettings remoteSettings)
            {
                Dictionary<string, object> matchCapabilities = this.GetCapabilitiesDictionary(capabilities);
    
                List<object> firstMatchCapabilitiesList = new List<object>();
                firstMatchCapabilitiesList.Add(matchCapabilities);
    
                Dictionary<string, object> specCompliantCapabilitiesDictionary = new Dictionary<string, object>();
                specCompliantCapabilitiesDictionary["firstMatch"] = firstMatchCapabilitiesList;
    
                parameters.Add("capabilities", specCompliantCapabilitiesDictionary);
            }
            else
            {
                parameters.Add("capabilities", remoteSettings.ToDictionary());
            }
    
            Response response = this.Execute(DriverCommand.NewSession, parameters);
    
            response.EnsureValueIsNotNull();
            if (response.Value is not Dictionary<string, object> rawCapabilities)
            {
                string errorMessage = string.Format(CultureInfo.InvariantCulture, "The new session command returned a value ('{0}') that is not a valid JSON object.", response.Value);
                throw new WebDriverException(errorMessage);
            }
    
            this.Capabilities = new ReturnedCapabilities(rawCapabilities);
    
            string sessionId = response.SessionId ?? throw new WebDriverException($"The remote end did not respond with ID of a session when it was required. {response.Value}");
            this.SessionId = new SessionId(sessionId);
        }
    
        /// <summary>
        /// Gets the capabilities as a dictionary.
        /// </summary>
        /// <param name="capabilitiesToConvert">The dictionary to return.</param>
        /// <returns>A Dictionary consisting of the capabilities requested.</returns>
        /// <remarks>This method is only transitional. Do not rely on it. It will be removed
        /// once browser driver capability formats stabilize.</remarks>
        /// <exception cref="ArgumentNullException">If <paramref name="capabilitiesToConvert"/> is <see langword="null"/>.</exception>
        protected virtual Dictionary<string, object> GetCapabilitiesDictionary(ICapabilities capabilitiesToConvert)
        {
            if (capabilitiesToConvert is null)
            {
                throw new ArgumentNullException(nameof(capabilitiesToConvert));
            }
    
            Dictionary<string, object> capabilitiesDictionary = new Dictionary<string, object>();
    
            foreach (KeyValuePair<string, object> entry in ((IHasCapabilitiesDictionary)capabilitiesToConvert).CapabilitiesDictionary)
            {
                if (CapabilityType.IsSpecCompliantCapabilityName(entry.Key))
                {
                    capabilitiesDictionary.Add(entry.Key, entry.Value);
                }
            }
    
            return capabilitiesDictionary;
        }
    
        /// <summary>
        /// Registers a command to be executed with this driver instance as an internally known driver command.
        /// </summary>
        /// <param name="commandName">The unique name of the command to register.</param>
        /// <param name="commandInfo">The <see cref="CommandInfo"/> object describing the command.</param>
        /// <returns><see langword="true"/> if the command was registered; otherwise, <see langword="false"/>.</returns>
        protected bool RegisterInternalDriverCommand(string commandName, [NotNullWhen(true)] CommandInfo? commandInfo)
        {
            return this.RegisterDriverCommand(commandName, commandInfo, true);
        }
    
        /// <summary>
        /// Stops the client from running
        /// </summary>
        /// <param name="disposing">if its in the process of disposing</param>
        protected virtual void Dispose(bool disposing)
        {
            try
            {
                if (this.SessionId is not null)
                {
                    this.Execute(DriverCommand.Quit, null);
                }
            }
            catch (NotImplementedException)
            {
            }
            catch (InvalidOperationException)
            {
            }
            catch (WebDriverException)
            {
            }
            finally
            {
                this.SessionId = null!;
            }
    
            this.CommandExecutor.Dispose();
        }
    
        private static void UnpackAndThrowOnError(Response errorResponse, string commandToExecute)
        {
            // Check the status code of the error, and only handle if not success.
            if (errorResponse.Status != WebDriverResult.Success)
            {
                if (errorResponse.Value is Dictionary<string, object?> errorAsDictionary)
                {
                    ErrorResponse errorResponseObject = new ErrorResponse(errorAsDictionary);
                    string errorMessage = errorResponseObject.Message;
                    switch (errorResponse.Status)
                    {
                        case WebDriverResult.NoSuchElement:
                            throw new NoSuchElementException(errorMessage);
    
                        case WebDriverResult.NoSuchFrame:
                            throw new NoSuchFrameException(errorMessage);
    
                        case WebDriverResult.UnknownCommand:
                            throw new NotImplementedException(errorMessage);
    
                        case WebDriverResult.ObsoleteElement:
                            throw new StaleElementReferenceException(errorMessage);
    
                        case WebDriverResult.ElementClickIntercepted:
                            throw new ElementClickInterceptedException(errorMessage);
    
                        case WebDriverResult.ElementNotInteractable:
                            throw new ElementNotInteractableException(errorMessage);
    
                        case WebDriverResult.InvalidElementState:
                            throw new InvalidElementStateException(errorMessage);
    
                        case WebDriverResult.Timeout:
                            throw new WebDriverTimeoutException(errorMessage);
    
                        case WebDriverResult.NoSuchWindow:
                            throw new NoSuchWindowException(errorMessage);
    
                        case WebDriverResult.InvalidCookieDomain:
                            throw new InvalidCookieDomainException(errorMessage);
    
                        case WebDriverResult.UnableToSetCookie:
                            throw new UnableToSetCookieException(errorMessage);
    
                        case WebDriverResult.AsyncScriptTimeout:
                            throw new WebDriverTimeoutException(errorMessage);
    
                        case WebDriverResult.UnexpectedAlertOpen:
                            // TODO(JimEvans): Handle the case where the unexpected alert setting
                            // has been set to "ignore", so there is still a valid alert to be
                            // handled.
                            string? alertText = null;
                            if (errorAsDictionary.TryGetValue("alert", out object? alert))
                            {
                                if (alert is Dictionary<string, object?> alertDescription
                                    && alertDescription.TryGetValue("text", out object? text))
                                {
                                    alertText = text?.ToString();
                                }
                            }
                            else if (errorAsDictionary.TryGetValue("data", out object? data))
                            {
                                if (data is Dictionary<string, object?> alertData
                                    && alertData.TryGetValue("text", out object? dataText))
                                {
                                    alertText = dataText?.ToString();
                                }
                            }
    
                            throw new UnhandledAlertException(errorMessage, alertText ?? string.Empty);
    
                        case WebDriverResult.NoAlertPresent:
                            throw new NoAlertPresentException(errorMessage);
    
                        case WebDriverResult.InvalidSelector:
                            throw new InvalidSelectorException(errorMessage);
    
                        case WebDriverResult.NoSuchDriver:
                            throw new WebDriverException(errorMessage);
    
                        case WebDriverResult.InvalidArgument:
                            throw new WebDriverArgumentException(errorMessage);
    
                        case WebDriverResult.UnexpectedJavaScriptError:
                            throw new JavaScriptException(errorMessage);
    
                        case WebDriverResult.MoveTargetOutOfBounds:
                            throw new MoveTargetOutOfBoundsException(errorMessage);
    
                        case WebDriverResult.NoSuchShadowRoot:
                            throw new NoSuchShadowRootException(errorMessage);
    
                        case WebDriverResult.DetachedShadowRoot:
                            throw new DetachedShadowRootException(errorMessage);
    
                        case WebDriverResult.InsecureCertificate:
                            throw new InsecureCertificateException(errorMessage);
    
                        case WebDriverResult.UnknownError:
                            throw new UnknownErrorException(errorMessage);
    
                        case WebDriverResult.UnknownMethod:
                            throw new UnknownMethodException(errorMessage);
    
                        case WebDriverResult.UnsupportedOperation:
                            throw new UnsupportedOperationException(errorMessage);
    
                        case WebDriverResult.NoSuchCookie:
                            throw new NoSuchCookieException(errorMessage);
    
                        default:
                            throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, "{0} ({1})", errorMessage, errorResponse.Status));
                    }
                }
    
                throw new WebDriverException($"The {commandToExecute} command returned an unexpected error. {errorResponse.Value}");
            }
        }
    
        /// <summary>
        /// Executes JavaScript in the context of the currently selected frame or window using a specific command.
        /// </summary>
        /// <param name="script">The JavaScript code to execute.</param>
        /// <param name="commandName">The name of the command to execute.</param>
        /// <param name="args">The arguments to the script.</param>
        /// <returns>The value returned by the script.</returns>
        protected object? ExecuteScriptCommand(string script, string commandName, params object?[]? args)
        {
            object?[] convertedArgs = ConvertArgumentsToJavaScriptObjects(args);
    
            Dictionary<string, object> parameters = new Dictionary<string, object>();
            parameters.Add("script", script);
    
            if (convertedArgs != null && convertedArgs.Length > 0)
            {
                parameters.Add("args", convertedArgs);
            }
            else
            {
                parameters.Add("args", new object[] { });
            }
    
            Response commandResponse = this.Execute(commandName, parameters);
            return this.ParseJavaScriptReturnValue(commandResponse.Value);
        }
    
        private static object? ConvertObjectToJavaScriptObject(object? arg)
        {
            IWebDriverObjectReference? argAsObjectReference = arg as IWebDriverObjectReference;
    
            if (argAsObjectReference == null && arg is IWrapsElement argAsWrapsElement)
            {
                argAsObjectReference = argAsWrapsElement.WrappedElement as IWebDriverObjectReference;
            }
    
            object? converted;
    
            if (arg is string || arg is float || arg is double || arg is int || arg is long || arg is bool || arg == null)
            {
                converted = arg;
            }
            else if (argAsObjectReference != null)
            {
                Dictionary<string, object> webDriverObjectReferenceDictionary = argAsObjectReference.ToDictionary();
                converted = webDriverObjectReferenceDictionary;
            }
            else if (arg is IDictionary argAsDictionary)
            {
                // Note that we must check for the argument being a dictionary before
                // checking for IEnumerable, since dictionaries also implement IEnumerable.
                // Additionally, JavaScript objects have property names as strings, so all
                // keys will be converted to strings.
                Dictionary<string, object?> dictionary = new Dictionary<string, object?>();
                foreach (DictionaryEntry argEntry in argAsDictionary)
                {
                    dictionary.Add(argEntry.Key.ToString()!, ConvertObjectToJavaScriptObject(argEntry.Value));
                }
    
                converted = dictionary;
            }
            else if (arg is IEnumerable argAsEnumerable)
            {
                List<object?> objectList = new List<object?>();
                foreach (object? item in argAsEnumerable)
                {
                    objectList.Add(ConvertObjectToJavaScriptObject(item));
                }
    
                converted = objectList.ToArray();
            }
            else
            {
                throw new ArgumentException("Argument is of an illegal type: " + arg.ToString(), nameof(arg));
            }
    
            return converted;
        }
    
        /// <summary>
        /// Converts the arguments to JavaScript objects.
        /// </summary>
        /// <param name="args">The arguments.</param>
        /// <returns>The list of the arguments converted to JavaScript objects.</returns>
        private static object?[] ConvertArgumentsToJavaScriptObjects(object?[]? args)
        {
            if (args == null)
            {
                return new object?[] { null };
            }
    
            for (int i = 0; i < args.Length; i++)
            {
                args[i] = ConvertObjectToJavaScriptObject(args[i]);
            }
    
            return args;
        }
    
        private object? ParseJavaScriptReturnValue(object? responseValue)
        {
            object? returnValue;
    
            if (responseValue is Dictionary<string, object?> resultAsDictionary)
            {
                if (this.elementFactory.ContainsElementReference(resultAsDictionary))
                {
                    returnValue = this.elementFactory.CreateElement(resultAsDictionary);
                }
                else if (ShadowRoot.TryCreate(this, resultAsDictionary, out ShadowRoot? shadowRoot))
                {
                    returnValue = shadowRoot;
                }
                else
                {
                    // Recurse through the dictionary, re-parsing each value.
                    string[] keyCopy = new string[resultAsDictionary.Keys.Count];
                    resultAsDictionary.Keys.CopyTo(keyCopy, 0);
                    foreach (string key in keyCopy)
                    {
                        resultAsDictionary[key] = this.ParseJavaScriptReturnValue(resultAsDictionary[key]);
                    }
    
                    returnValue = resultAsDictionary;
                }
            }
            else if (responseValue is object?[] resultAsArray)
            {
                bool allElementsAreWebElements = true;
                List<object?> toReturn = new List<object?>(resultAsArray.Length);
                foreach (object? item in resultAsArray)
                {
                    object? parsedItem = this.ParseJavaScriptReturnValue(item);
                    if (parsedItem is not IWebElement)
                    {
                        allElementsAreWebElements = false;
                    }
    
                    toReturn.Add(parsedItem);
                }
    
                if (toReturn.Count > 0 && allElementsAreWebElements)
                {
                    List<IWebElement> elementList = new List<IWebElement>(resultAsArray.Length);
                    foreach (object? listItem in toReturn)
                    {
                        elementList.Add((IWebElement)listItem!);
                    }
    
                    returnValue = elementList.AsReadOnly();
                }
                else
                {
                    returnValue = toReturn.AsReadOnly();
                }
            }
            else
            {
                returnValue = responseValue;
            }
    
            return returnValue;
        }
    
        /// <summary>
        /// Creates a Virtual Authenticator.
        /// </summary>
        /// <param name="options"><see href="https://w3c.github.io/webauthn/#sctn-automation-virtual-authenticators">Virtual Authenticator Options</see>.</param>
        /// <returns> Authenticator id as string </returns>
        /// <exception cref="ArgumentNullException">If <paramref name="options"/> is <see langword="null"/>.</exception>
        public string AddVirtualAuthenticator(VirtualAuthenticatorOptions options)
        {
            if (options is null)
            {
                throw new ArgumentNullException(nameof(options));
            }
    
            Response commandResponse = this.Execute(DriverCommand.AddVirtualAuthenticator, options.ToDictionary());
    
            commandResponse.EnsureValueIsNotNull();
            string id = (string)commandResponse.Value;
            this.AuthenticatorId = id;
            return id;
        }
    
        /// <summary>
        /// Removes the Virtual Authenticator
        /// </summary>
        /// <param name="authenticatorId">Id as string that uniquely identifies a Virtual Authenticator.</param>
        /// <exception cref="ArgumentNullException">If <paramref name="authenticatorId"/> is <see langword="null"/>.</exception>
        public void RemoveVirtualAuthenticator(string authenticatorId)
        {
            if (authenticatorId is null)
            {
                throw new ArgumentNullException(nameof(authenticatorId));
            }
    
            Dictionary<string, object> parameters = new Dictionary<string, object>();
            parameters.Add("authenticatorId", authenticatorId);
    
            this.Execute(DriverCommand.RemoveVirtualAuthenticator, parameters);
            this.AuthenticatorId = null;
        }
    
        /// <summary>
        /// Gets the cached virtual authenticator ID, or <see langword="null"/> if no authenticator ID is set.
        /// </summary>
        public string? AuthenticatorId { get; private set; }
    
        /// <summary>
        /// Add a credential to the Virtual Authenticator/
        /// </summary>
        /// <param name="credential"> The credential to be stored in the Virtual Authenticator</param>
        /// <exception cref="ArgumentNullException">If <paramref name="credential"/> is <see langword="null"/>.</exception>
        /// <exception cref="InvalidOperationException">If a Virtual Authenticator has not been added yet.</exception>
        public void AddCredential(Credential credential)
        {
            if (credential is null)
            {
                throw new ArgumentNullException(nameof(credential));
            }
    
            string authenticatorId = this.AuthenticatorId ?? throw new InvalidOperationException("Virtual Authenticator needs to be added before it can perform operations");
    
            Dictionary<string, object> parameters = new Dictionary<string, object>(credential.ToDictionary());
            parameters.Add("authenticatorId", authenticatorId);
    
            this.Execute(driverCommandToExecute: DriverCommand.AddCredential, parameters);
        }
    
        /// <summary>
        /// Retrieves all the credentials stored in the Virtual Authenticator
        /// </summary>
        /// <returns> List of credentials </returns>
        /// <exception cref="InvalidOperationException">If a Virtual Authenticator has not been added yet.</exception>
        public List<Credential> GetCredentials()
        {
            string authenticatorId = this.AuthenticatorId ?? throw new InvalidOperationException("Virtual Authenticator needs to be added before it can perform operations");
    
            Dictionary<string, object> parameters = new Dictionary<string, object>();
            parameters.Add("authenticatorId", authenticatorId);
    
            Response getCredentialsResponse = this.Execute(driverCommandToExecute: DriverCommand.GetCredentials, parameters);
    
            getCredentialsResponse.EnsureValueIsNotNull();
            if (getCredentialsResponse.Value is not object?[] credentialsList)
            {
                throw new WebDriverException($"Get credentials call succeeded, but the response was not a list of credentials: {getCredentialsResponse.Value}");
            }
    
            List<Credential> credentials = new List<Credential>(credentialsList.Length);
            foreach (object? dictionary in credentialsList)
            {
                Credential credential = Credential.FromDictionary((Dictionary<string, object>)dictionary!);
                credentials.Add(credential);
            }
    
            return credentials;
        }
    
        /// <summary>
        /// Removes the credential identified by the credentialId from the Virtual Authenticator.
        /// </summary>
        /// <param name="credentialId"> The id as byte array that uniquely identifies a credential </param>
        /// <exception cref="ArgumentNullException">If <paramref name="credentialId"/> is <see langword="null"/>.</exception>
        /// <exception cref="InvalidOperationException">If a Virtual Authenticator has not been added yet.</exception>
        public void RemoveCredential(byte[] credentialId)
        {
            RemoveCredential(Base64UrlEncoder.Encode(credentialId));
        }
    
        /// <summary>
        /// Removes the credential identified by the credentialId from the Virtual Authenticator.
        /// </summary>
        /// <param name="credentialId"> The id as string that uniquely identifies a credential </param>
        /// <exception cref="ArgumentNullException">If <paramref name="credentialId"/> is <see langword="null"/>.</exception>
        /// <exception cref="InvalidOperationException">If a Virtual Authenticator has not been added yet.</exception>
        public void RemoveCredential(string credentialId)
        {
            if (credentialId is null)
            {
                throw new ArgumentNullException(nameof(credentialId));
            }
    
            string authenticatorId = this.AuthenticatorId ?? throw new InvalidOperationException("Virtual Authenticator needs to be added before it can perform operations");
    
            Dictionary<string, object> parameters = new Dictionary<string, object>();
            parameters.Add("authenticatorId", authenticatorId);
            parameters.Add("credentialId", credentialId);
    
            this.Execute(driverCommandToExecute: DriverCommand.RemoveCredential, parameters);
        }
    
        /// <summary>
        /// Removes all the credentials stored in the Virtual Authenticator.
        /// </summary>
        /// <exception cref="InvalidOperationException">If a Virtual Authenticator has not been added yet.</exception>
        public void RemoveAllCredentials()
        {
            string authenticatorId = this.AuthenticatorId ?? throw new InvalidOperationException("Virtual Authenticator needs to be added before it can perform operations");
    
            Dictionary<string, object> parameters = new Dictionary<string, object>();
            parameters.Add("authenticatorId", authenticatorId);
    
            this.Execute(driverCommandToExecute: DriverCommand.RemoveAllCredentials, parameters);
        }
    
        /// <summary>
        ///  Sets the isUserVerified property for the Virtual Authenticator.
        /// </summary>
        /// <param name="verified">The boolean value representing value to be set </param>
        public void SetUserVerified(bool verified)
        {
            string authenticatorId = this.AuthenticatorId ?? throw new InvalidOperationException("Virtual Authenticator needs to be added before it can perform operations");
    
            Dictionary<string, object> parameters = new Dictionary<string, object>();
            parameters.Add("authenticatorId", authenticatorId);
            parameters.Add("isUserVerified", verified);
    
            this.Execute(driverCommandToExecute: DriverCommand.SetUserVerified, parameters);
        }
    }

    Copy link
    Contributor

    qodo-merge-pro bot commented Apr 20, 2025

    PR Code Suggestions ✨

    Latest suggestions up to 866f81c
    Explore these optional code suggestions:

    CategorySuggestion                                                                                                                                    Impact
    Possible issue
    Consistent empty selector behavior

    The test expects that finding elements with an empty CSS selector should throw
    an exception, but in many browsers and WebDriver implementations, this might
    return an empty collection instead. Consider making the test consistent with the
    behavior of FindingMultipleElementsByEmptyIdShouldReturnEmptyList() which
    expects an empty collection rather than an exception.

    dotnet/test/common/ElementFindingTest.cs [625-629]

     [Test]
    -public void FindingMultipleElementsByEmptyCssSelectorShouldThrow()
    +public void FindingMultipleElementsByEmptyCssSelectorShouldReturnEmptyList()
     {
         driver.Url = xhtmlTestPage;
    -    Assert.That(() => driver.FindElements(By.CssSelector("")), Throws.InstanceOf<WebDriverException>());
    +    ReadOnlyCollection<IWebElement> elements = driver.FindElements(By.CssSelector(""));
    +    Assert.That(elements, Is.Empty);
     }

    [To ensure code accuracy, apply this suggestion manually]

    Suggestion importance[1-10]: 7

    __

    Why: The suggestion correctly identifies an inconsistency in how empty selectors are handled. The test expects an exception for empty CSS selectors but expects empty collections for empty ID selectors, which could lead to test failures if browser implementations change. Making the behavior consistent improves test reliability.

    Medium
    • More

    Previous suggestions

    Suggestions up to commit 866f81c
    CategorySuggestion                                                                                                                                    Impact
    General
    Remove null-forgiving operator

    The code is using the null-forgiving operator (!) to suppress the non-null
    warning on SessionId, but this could lead to potential null reference
    exceptions. Instead, consider using a proper null check before accessing the
    property or making the property nullable.

    dotnet/src/webdriver/WebDriver.cs [705]

    -this.SessionId = null!;
    +this.SessionId = null;
    Suggestion importance[1-10]: 3

    __

    Why: The suggestion correctly identifies the use of the null-forgiving operator, but its impact is limited. The property is marked with [MemberNotNull] attribute elsewhere, indicating it should never be null during normal operation. Removing the operator would cause compiler warnings since SessionId is likely non-nullable by design.

    Low

    @nvborisenko
    Copy link
    Member Author

    Hey bot, your opinion is useful, but please suggest rather than decide.

    @nvborisenko nvborisenko reopened this Apr 20, 2025
    @nvborisenko nvborisenko merged commit 56c9d29 into SeleniumHQ:trunk Apr 20, 2025
    25 checks passed
    @nvborisenko nvborisenko deleted the dotnet-namespace-file-scoped branch April 20, 2025 20:21
    Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
    Labels
    B-support Issue or PR related to support classes C-dotnet .NET Bindings Review effort 2/5
    Projects
    None yet
    Development

    Successfully merging this pull request may close these issues.

    2 participants