blob: 483ceb03130a421e72866a4c6f8c548d5a8ef5ee [file] [log] [blame]
// <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 &lt;input type='file'/&gt; 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;
}
}