Path: blob/trunk/dotnet/src/webdriver/DriverOptions.cs
2884 views
// <copyright file="DriverOptions.cs" company="Selenium Committers"> // Licensed to the Software Freedom Conservancy (SFC) under one // or more contributor license agreements. See the NOTICE file // distributed with this work for additional information // regarding copyright ownership. The SFC licenses this file // to you under the Apache License, Version 2.0 (the // "License"); you may not use this file except in compliance // with the License. You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, // software distributed under the License is distributed on an // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY // KIND, either express or implied. See the License for the // specific language governing permissions and limitations // under the License. // </copyright> using OpenQA.Selenium.Internal; using OpenQA.Selenium.Remote; using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Globalization; namespace OpenQA.Selenium; /// <summary> /// Specifies the behavior of handling unexpected alerts in the IE driver. /// </summary> public enum UnhandledPromptBehavior { /// <summary> /// Indicates the behavior is not set. /// </summary> Default, /// <summary> /// Ignore unexpected alerts, such that the user must handle them. /// </summary> Ignore, /// <summary> /// Accept unexpected alerts. /// </summary> Accept, /// <summary> /// Dismiss unexpected alerts. /// </summary> Dismiss, /// <summary> /// Accepts unexpected alerts and notifies the user that the alert has /// been accepted by throwing an <see cref="UnhandledAlertException"/> /// </summary> AcceptAndNotify, /// <summary> /// Dismisses unexpected alerts and notifies the user that the alert has /// been dismissed by throwing an <see cref="UnhandledAlertException"/> /// </summary> DismissAndNotify } /// <summary> /// Specifies the behavior of waiting for page loads in the driver. /// </summary> public enum PageLoadStrategy { /// <summary> /// Indicates the behavior is not set. /// </summary> Default, /// <summary> /// Waits for pages to load and ready state to be 'complete'. /// </summary> Normal, /// <summary> /// Waits for pages to load and for ready state to be 'interactive' or 'complete'. /// </summary> Eager, /// <summary> /// Does not wait for pages to load, returning immediately. /// </summary> None } internal class Timeout { public TimeSpan? Script { get; set; } public TimeSpan? PageLoad { get; set; } public TimeSpan? ImplicitWait { get; set; } public Dictionary<string, object> ToCapabilities() { var timeoutCapabilities = new Dictionary<string, object>(); if (Script.HasValue) timeoutCapabilities.Add("script", Script.Value.TotalMilliseconds); if (PageLoad.HasValue) timeoutCapabilities.Add("pageLoad", PageLoad.Value.TotalMilliseconds); if (ImplicitWait.HasValue) timeoutCapabilities.Add("implicit", ImplicitWait.Value.TotalMilliseconds); return timeoutCapabilities; } } /// <summary> /// Base class for managing options specific to a browser driver. /// </summary> public abstract class DriverOptions { private readonly Dictionary<string, object> additionalCapabilities = new Dictionary<string, object>(); private readonly Dictionary<string, LogLevel> loggingPreferences = new Dictionary<string, LogLevel>(); private readonly Dictionary<string, string> knownCapabilityNames = new Dictionary<string, string>(); /// <summary> /// Initializes a new instance of the <see cref="DriverOptions"/> class. /// </summary> protected DriverOptions() { this.AddKnownCapabilityName(CapabilityType.BrowserName, "BrowserName property"); this.AddKnownCapabilityName(CapabilityType.BrowserVersion, "BrowserVersion property"); this.AddKnownCapabilityName(CapabilityType.PlatformName, "PlatformName property"); this.AddKnownCapabilityName(CapabilityType.Proxy, "Proxy property"); this.AddKnownCapabilityName(CapabilityType.UnhandledPromptBehavior, "UnhandledPromptBehavior property"); this.AddKnownCapabilityName(CapabilityType.PageLoadStrategy, "PageLoadStrategy property"); this.AddKnownCapabilityName(CapabilityType.UseStrictFileInteractability, "UseStrictFileInteractability property"); this.AddKnownCapabilityName(CapabilityType.WebSocketUrl, "UseWebSocketUrl property"); this.AddKnownCapabilityName(CapabilityType.EnableDownloads, "EnableDownloads property"); } /// <summary> /// Gets or sets the name of the browser. /// </summary> public string? BrowserName { get; protected set; } /// <summary> /// Gets or sets the version of the browser. /// </summary> public string? BrowserVersion { get; set; } /// <summary> /// Gets or sets the name of the platform on which the browser is running. /// </summary> public string? PlatformName { get; set; } /// <summary> /// Gets or sets a value indicating whether the browser should accept self-signed /// SSL certificates. /// </summary> public bool? AcceptInsecureCertificates { get; set; } /// <summary> /// Gets or sets a value indicating whether the driver should request a URL to /// a WebSocket to be used for bidirectional communication. /// </summary> public bool? UseWebSocketUrl { get; set; } /// <summary> /// Gets or sets the value for describing how unexpected alerts are to be handled in the browser. /// Defaults to <see cref="UnhandledPromptBehavior.Default"/>. /// </summary> public UnhandledPromptBehavior UnhandledPromptBehavior { get; set; } = UnhandledPromptBehavior.Default; /// <summary> /// Gets or sets the value for describing how the browser is to wait for pages to load in the browser. /// Defaults to <see cref="PageLoadStrategy.Default"/>. /// </summary> public PageLoadStrategy PageLoadStrategy { get; set; } = PageLoadStrategy.Default; /// <summary> /// Gets or sets the <see cref="Proxy"/> to be used with this browser. /// </summary> public Proxy? Proxy { get; set; } /// <summary> /// Gets or sets a value indicating whether <input type='file'/> elements /// must be visible to allow uploading of files. /// </summary> public bool UseStrictFileInteractability { get; set; } /// <summary> /// Gets or sets a value indicating whether files may be downloaded from remote node. /// </summary> public bool? EnableDownloads { get; set; } /// <summary> /// Gets or sets the asynchronous script timeout, which is the amount /// of time the driver should wait when executing JavaScript asynchronously. /// This timeout only affects the <see cref="IJavaScriptExecutor.ExecuteAsyncScript(string, object[])"/> /// method. /// </summary> public TimeSpan? ScriptTimeout { get; set; } /// <summary> /// Gets or sets the page load timeout, which is the amount of time the driver /// should wait for a page to load when setting the <see cref="IWebDriver.Url"/> /// property. /// </summary> public TimeSpan? PageLoadTimeout { get; set; } /// <summary> /// Gets or sets the implicit wait timeout, which is the amount of time the /// driver should wait when searching for an element if it is not immediately /// present. /// </summary> /// <remarks> /// When searching for a single element, the driver should poll the page /// until the element has been found, or this timeout expires before throwing /// a <see cref="NoSuchElementException"/>. When searching for multiple elements, /// the driver should poll the page until at least one element has been found /// or this timeout has expired. /// <para> /// Increasing the implicit wait timeout should be used judiciously as it /// will have an adverse effect on test run time, especially when used with /// slower location strategies like XPath. /// </para> /// </remarks> public TimeSpan? ImplicitWaitTimeout { get; set; } /// <summary> /// Set or Get the location of the browser /// Override in subclass /// </summary> public virtual string? BinaryLocation { get => null; set => throw new NotImplementedException(); } /// <summary> /// Provides a means to add additional capabilities not yet added as type safe options /// for the specific browser driver. /// </summary> /// <param name="optionName">The name of the capability to add.</param> /// <param name="optionValue">The value of the capability to add.</param> /// <exception cref="ArgumentException"> /// thrown when attempting to add a capability for which there is already a type safe option, or /// when <paramref name="optionName"/> is <see langword="null"/> or the empty string. /// </exception> /// <remarks>Calling <see cref="AddAdditionalOption(string, object)"/> /// where <paramref name="optionName"/> has already been added will overwrite the /// existing value with the new value in <paramref name="optionValue"/>. /// </remarks> public virtual void AddAdditionalOption(string optionName, object optionValue) { this.ValidateCapabilityName(optionName); this.additionalCapabilities[optionName] = optionValue; } /// <summary> /// Returns the <see cref="ICapabilities"/> for the specific browser driver with these /// options included as capabilities. This does not copy the options. Further /// changes will be reflected in the returned capabilities. /// </summary> /// <returns>The <see cref="ICapabilities"/> for browser driver with these options.</returns> public abstract ICapabilities ToCapabilities(); /// <summary> /// Compares this <see cref="DriverOptions"/> object with another to see if there /// are merge conflicts between them. /// </summary> /// <param name="other">The <see cref="DriverOptions"/> object to compare with.</param> /// <returns>A <see cref="DriverOptionsMergeResult"/> object containing the status of the attempted merge.</returns> /// <exception cref="ArgumentNullException">If <paramref name="other"/> is <see langword="null"/>.</exception> public virtual DriverOptionsMergeResult GetMergeResult(DriverOptions other) { if (other is null) { throw new ArgumentNullException(nameof(other)); } DriverOptionsMergeResult result = new DriverOptionsMergeResult(); if (this.BrowserName != null && other.BrowserName != null) { result.IsMergeConflict = true; result.MergeConflictOptionName = "BrowserName"; return result; } if (this.BrowserVersion != null && other.BrowserVersion != null) { result.IsMergeConflict = true; result.MergeConflictOptionName = "BrowserVersion"; return result; } if (this.PlatformName != null && other.PlatformName != null) { result.IsMergeConflict = true; result.MergeConflictOptionName = "PlatformName"; return result; } if (this.Proxy != null && other.Proxy != null) { result.IsMergeConflict = true; result.MergeConflictOptionName = "Proxy"; return result; } if (this.UnhandledPromptBehavior != UnhandledPromptBehavior.Default && other.UnhandledPromptBehavior != UnhandledPromptBehavior.Default) { result.IsMergeConflict = true; result.MergeConflictOptionName = "UnhandledPromptBehavior"; return result; } if (this.PageLoadStrategy != PageLoadStrategy.Default && other.PageLoadStrategy != PageLoadStrategy.Default) { result.IsMergeConflict = true; result.MergeConflictOptionName = "PageLoadStrategy"; return result; } return result; } /// <summary> /// Sets the logging preferences for this driver. /// </summary> /// <param name="logType">The type of log for which to set the preference. /// Known log types can be found in the <see cref="LogType"/> class.</param> /// <param name="logLevel">The <see cref="LogLevel"/> value to which to set the log level.</param> public void SetLoggingPreference(string logType, LogLevel logLevel) { this.loggingPreferences[logType] = logLevel; } /// <summary> /// Returns the current options as a <see cref="Dictionary{TKey, TValue}"/>. /// </summary> /// <returns>The current options as a <see cref="Dictionary{TKey, TValue}"/>.</returns> internal IDictionary<string, object>? ToDictionary() { ICapabilities? capabilities = this.ToCapabilities(); if (capabilities is not IHasCapabilitiesDictionary desired) { return null; } return desired.CapabilitiesDictionary; } /// <summary> /// Validates the name of the capability to verify it is not a capability /// for which a type-safe property or method already exists. /// </summary> /// <param name="capabilityName">The name of the capability to validate.</param> /// <exception cref="ArgumentException"> /// thrown when attempting to add a capability for which there is already a type safe option, or /// when <paramref name="capabilityName"/> is <see langword="null"/> or the empty string. /// </exception> protected void ValidateCapabilityName([NotNull] string? capabilityName) { if (capabilityName is null || string.IsNullOrEmpty(capabilityName)) { throw new ArgumentException("Capability name may not be null an empty string.", nameof(capabilityName)); } if (this.TryGetKnownCapability(capabilityName!, out string? typeSafeOptionName)) { string message = string.Format(CultureInfo.InvariantCulture, "There is already an option for the {0} capability. Please use the {1} instead.", capabilityName, typeSafeOptionName); throw new ArgumentException(message, nameof(capabilityName)); } } /// <summary> /// Adds a known capability to the list of known capabilities and associates it /// with the type-safe property name of the options class to be used instead. /// </summary> /// <param name="capabilityName">The name of the capability.</param> /// <param name="typeSafeOptionName">The name of the option property or method to be used instead.</param> protected void AddKnownCapabilityName(string capabilityName, string typeSafeOptionName) { this.knownCapabilityNames[capabilityName] = typeSafeOptionName; } /// <summary> /// Remove a capability from the list of known capabilities /// </summary> /// <param name="capabilityName">The name of the capability to be removed.</param> protected void RemoveKnownCapabilityName(string? capabilityName) { if (capabilityName is not null) { this.knownCapabilityNames.Remove(capabilityName); } } /// <summary> /// Gets a value indicating whether the specified capability name is a known capability name which has a type-safe option. /// </summary> /// <param name="capabilityName">The name of the capability to check.</param> /// <returns><see langword="true"/> if the capability name is known; otherwise <see langword="false"/>.</returns> protected bool IsKnownCapabilityName(string capabilityName) { return this.knownCapabilityNames.ContainsKey(capabilityName); } /// <summary> /// Gets a value indicating whether the specified capability name is a known capability name which has a type-safe option. /// </summary> /// <param name="capabilityName">The name of the capability to check.</param> /// <param name="typeSafeOptionName">The name of the type-safe option for the given capability name, or <see langword="null"/> if not found.</param> /// <returns><see langword="true"/> if the capability name is known; otherwise <see langword="false"/>.</returns> protected bool TryGetKnownCapability(string capabilityName, [NotNullWhen(true)] out string? typeSafeOptionName) { return this.knownCapabilityNames.TryGetValue(capabilityName, out typeSafeOptionName); } /// <summary> /// Gets the name of the type-safe option for a given capability name. /// </summary> /// <param name="capabilityName">The name of the capability to check.</param> /// <returns>The name of the type-safe option for the given capability name.</returns> protected string GetTypeSafeOptionName(string capabilityName) { if (!this.IsKnownCapabilityName(capabilityName)) { return string.Empty; } return this.knownCapabilityNames[capabilityName]; } /// <summary> /// Generates the logging preferences dictionary for transmission as a desired capability. /// </summary> /// <returns>The dictionary containing the logging preferences.</returns> protected Dictionary<string, object>? GenerateLoggingPreferencesDictionary() { if (this.loggingPreferences.Count == 0) { return null; } Dictionary<string, object> loggingPreferenceCapability = new Dictionary<string, object>(); foreach (string logType in this.loggingPreferences.Keys) { loggingPreferenceCapability[logType] = this.loggingPreferences[logType].ToString().ToUpperInvariant(); } return loggingPreferenceCapability; } /// <summary> /// Generates the current options as a capabilities object for further processing. /// </summary> /// <param name="isSpecificationCompliant">A value indicating whether to generate capabilities compliant with the W3C WebDriver Specification.</param> /// <returns>A <see cref="IWritableCapabilities"/> object representing the current options for further processing.</returns> protected IWritableCapabilities GenerateDesiredCapabilities(bool isSpecificationCompliant) { DesiredCapabilities capabilities = new DesiredCapabilities(); if (!string.IsNullOrEmpty(this.BrowserName)) { capabilities.SetCapability(CapabilityType.BrowserName, this.BrowserName!); } if (!string.IsNullOrEmpty(this.BrowserVersion)) { capabilities.SetCapability(CapabilityType.BrowserVersion, this.BrowserVersion!); } if (!string.IsNullOrEmpty(this.PlatformName)) { capabilities.SetCapability(CapabilityType.PlatformName, this.PlatformName!); } if (this.AcceptInsecureCertificates.HasValue) { capabilities.SetCapability(CapabilityType.AcceptInsecureCertificates, this.AcceptInsecureCertificates); } if (this.UseWebSocketUrl.HasValue) { capabilities.SetCapability(CapabilityType.WebSocketUrl, this.UseWebSocketUrl); } if (this.EnableDownloads.HasValue) { capabilities.SetCapability(CapabilityType.EnableDownloads, this.EnableDownloads); } if (this.UseStrictFileInteractability) { capabilities.SetCapability(CapabilityType.UseStrictFileInteractability, true); } if (this.PageLoadStrategy != PageLoadStrategy.Default) { string pageLoadStrategySetting = "normal"; switch (this.PageLoadStrategy) { case PageLoadStrategy.Eager: pageLoadStrategySetting = "eager"; break; case PageLoadStrategy.None: pageLoadStrategySetting = "none"; break; } capabilities.SetCapability(CapabilityType.PageLoadStrategy, pageLoadStrategySetting); } if (this.UnhandledPromptBehavior != UnhandledPromptBehavior.Default) { string unhandledPropmtBehaviorSetting = "ignore"; switch (this.UnhandledPromptBehavior) { case UnhandledPromptBehavior.Accept: unhandledPropmtBehaviorSetting = "accept"; break; case UnhandledPromptBehavior.Dismiss: unhandledPropmtBehaviorSetting = "dismiss"; break; case UnhandledPromptBehavior.AcceptAndNotify: unhandledPropmtBehaviorSetting = "accept and notify"; break; case UnhandledPromptBehavior.DismissAndNotify: unhandledPropmtBehaviorSetting = "dismiss and notify"; break; } capabilities.SetCapability(CapabilityType.UnhandledPromptBehavior, unhandledPropmtBehaviorSetting); } if (this.Proxy != null) { Dictionary<string, object?>? proxyCapability = this.Proxy.ToCapability(); if (!isSpecificationCompliant) { proxyCapability = this.Proxy.ToLegacyCapability(); } if (proxyCapability != null) { capabilities.SetCapability(CapabilityType.Proxy, proxyCapability); } } if (this.ScriptTimeout.HasValue || this.PageLoadTimeout.HasValue || this.ImplicitWaitTimeout.HasValue) { var timeouts = new Timeout { Script = this.ScriptTimeout, PageLoad = this.PageLoadTimeout, ImplicitWait = this.ImplicitWaitTimeout }; capabilities.SetCapability(CapabilityType.Timeouts, timeouts.ToCapabilities()); } foreach (KeyValuePair<string, object> pair in this.additionalCapabilities) { capabilities.SetCapability(pair.Key, pair.Value); } return capabilities; } }