Skip to content
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

[dotnet] Allow RemoteSessionSettings to use any value for metadata #14726

Merged
91 changes: 14 additions & 77 deletions dotnet/src/webdriver/Remote/RemoteSessionSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
using System.Collections.Generic;
using System.Globalization;
using System.Text.Json;
using System.Text.Json.Nodes;

namespace OpenQA.Selenium
{
Expand All @@ -35,7 +36,6 @@ public class RemoteSessionSettings : ICapabilities
private const string AlwaysMatchCapabilityName = "alwaysMatch";

private readonly List<string> reservedSettingNames = new List<string>() { FirstMatchCapabilityName, AlwaysMatchCapabilityName };
private DriverOptions mustMatchDriverOptions;
nvborisenko marked this conversation as resolved.
Show resolved Hide resolved
private List<DriverOptions> firstMatchOptions = new List<DriverOptions>();
private Dictionary<string, object> remoteMetadataSettings = new Dictionary<string, object>();

Expand All @@ -61,7 +61,7 @@ public RemoteSessionSettings()
/// </param>
public RemoteSessionSettings(DriverOptions mustMatchDriverOptions, params DriverOptions[] firstMatchDriverOptions)
{
this.mustMatchDriverOptions = mustMatchDriverOptions;
this.MustMatchDriverOptions = mustMatchDriverOptions;
foreach (DriverOptions firstMatchOption in firstMatchDriverOptions)
{
this.AddFirstMatchDriverOption(firstMatchOption);
Expand All @@ -71,18 +71,12 @@ public RemoteSessionSettings(DriverOptions mustMatchDriverOptions, params Driver
/// <summary>
/// Gets a value indicating the options that must be matched by the remote end to create a session.
/// </summary>
internal DriverOptions MustMatchDriverOptions
{
get { return this.mustMatchDriverOptions; }
}
internal DriverOptions MustMatchDriverOptions { get; private set; }

/// <summary>
/// Gets a value indicating the number of options that may be matched by the remote end to create a session.
/// </summary>
internal int FirstMatchOptionsCount
{
get { return this.firstMatchOptions.Count; }
}
internal int FirstMatchOptionsCount => this.firstMatchOptions.Count;

/// <summary>
/// Gets the capability value with the specified name.
Expand All @@ -92,6 +86,7 @@ internal int FirstMatchOptionsCount
/// <exception cref="ArgumentException">
/// The specified capability name is not in the set of capabilities.
/// </exception>
/// <exception cref="ArgumentNullException">If <paramref name="capabilityName"/> is null.</exception>
public object this[string capabilityName]
{
get
Expand Down Expand Up @@ -121,18 +116,10 @@ public object this[string capabilityName]
/// </summary>
/// <param name="settingName">The name of the setting to set.</param>
/// <param name="settingValue">The value of the setting.</param>
/// <remarks>
/// The value to be set must be serializable to JSON for transmission
/// across the wire to the remote end. To be JSON-serializable, the value
/// must be a string, a numeric value, a boolean value, an object that
/// implmeents <see cref="IEnumerable"/> that contains JSON-serializable
/// objects, or a <see cref="Dictionary{TKey, TValue}"/> where the keys
/// are strings and the values are JSON-serializable.
/// </remarks>
/// <exception cref="ArgumentException">
/// Thrown if the setting name is null, the empty string, or one of the
/// reserved names of metadata settings; or if the setting value is not
/// JSON serializable.
/// <para>If the setting name is null or empty.</para>
/// <para>-or-</para>
/// <para>If one of the reserved names of metadata settings.</para>
/// </exception>
public void AddMetadataSetting(string settingName, object settingValue)
{
Expand All @@ -146,11 +133,6 @@ public void AddMetadataSetting(string settingName, object settingValue)
throw new ArgumentException(string.Format("'{0}' is a reserved name for a metadata setting, and cannot be used as a name.", settingName), nameof(settingName));
}

if (!this.IsJsonSerializable(settingValue))
{
throw new ArgumentException("Metadata setting value must be JSON serializable.", nameof(settingValue));
}

this.remoteMetadataSettings[settingName] = settingValue;
}

Expand All @@ -161,9 +143,9 @@ public void AddMetadataSetting(string settingName, object settingValue)
/// <param name="options">The <see cref="DriverOptions"/> to add to the list of "first matched" options.</param>
public void AddFirstMatchDriverOption(DriverOptions options)
{
if (mustMatchDriverOptions != null)
if (MustMatchDriverOptions != null)
{
DriverOptionsMergeResult mergeResult = mustMatchDriverOptions.GetMergeResult(options);
DriverOptionsMergeResult mergeResult = MustMatchDriverOptions.GetMergeResult(options);
if (mergeResult.IsMergeConflict)
{
string msg = string.Format(CultureInfo.InvariantCulture, "You cannot request the same capability in both must-match and first-match capabilities. You are attempting to add a first-match driver options object that defines a capability, '{0}', that is already defined in the must-match driver options.", mergeResult.MergeConflictOptionName);
Expand Down Expand Up @@ -198,7 +180,7 @@ public void SetMustMatchDriverOptions(DriverOptions options)
}
}

this.mustMatchDriverOptions = options;
this.MustMatchDriverOptions = options;
}

/// <summary>
Expand Down Expand Up @@ -256,7 +238,7 @@ public Dictionary<string, object> ToDictionary()
capabilitiesDictionary[remoteMetadataSetting.Key] = remoteMetadataSetting.Value;
}

if (this.mustMatchDriverOptions != null)
if (this.MustMatchDriverOptions != null)
{
capabilitiesDictionary["alwaysMatch"] = GetAlwaysMatchOptionsAsSerializableDictionary();
}
Expand Down Expand Up @@ -292,63 +274,18 @@ internal DriverOptions GetFirstMatchDriverOptions(int firstMatchIndex)

private IDictionary<string, object> GetAlwaysMatchOptionsAsSerializableDictionary()
{
return this.mustMatchDriverOptions.ToDictionary();
return this.MustMatchDriverOptions.ToDictionary();
}

private List<object> GetFirstMatchOptionsAsSerializableList()
{
List<object> optionsMatches = new List<object>();
List<object> optionsMatches = new List<object>(this.firstMatchOptions.Count);
foreach (DriverOptions options in this.firstMatchOptions)
{
optionsMatches.Add(options.ToDictionary());
}

return optionsMatches;
}

private bool IsJsonSerializable(object arg)
{
IEnumerable argAsEnumerable = arg as IEnumerable;
IDictionary argAsDictionary = arg as IDictionary;

if (arg is string || arg is float || arg is double || arg is int || arg is long || arg is bool || arg == null)
{
return true;
}
else if (argAsDictionary != null)
{
foreach (object key in argAsDictionary.Keys)
{
if (!(key is string))
{
return false;
}
}

foreach (object value in argAsDictionary.Values)
{
if (!IsJsonSerializable(value))
{
return false;
}
}
}
else if (argAsEnumerable != null)
{
foreach (object item in argAsEnumerable)
{
if (!IsJsonSerializable(item))
{
return false;
}
}
}
else
{
return false;
}

return true;
}
}
}
64 changes: 64 additions & 0 deletions dotnet/test/remote/RemoteSessionCreationTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@
// </copyright>

using NUnit.Framework;
using System.Collections.Generic;
using System.Text.Json;
using System.Text.Json.Nodes;

namespace OpenQA.Selenium.Remote
{
Expand Down Expand Up @@ -68,5 +71,66 @@ public void CreateEdgeRemoteSession()
edge.Quit();
}
}

[Test]
public void ShouldSetRemoteSessionSettingsMetadata()
{
var settings = new RemoteSessionSettings();

Assert.That(settings.HasCapability("a"), Is.False);

settings.AddMetadataSetting("a", null);
Assert.That(settings.HasCapability("a"));
Assert.That(settings.GetCapability("a"), Is.Null);

settings.AddMetadataSetting("a", true);
Assert.That(settings.HasCapability("a"));
Assert.That(settings.GetCapability("a"), Is.True);

settings.AddMetadataSetting("a", false);
Assert.That(settings.HasCapability("a"));
Assert.That(settings.GetCapability("a"), Is.False);

settings.AddMetadataSetting("a", 123);
Assert.That(settings.HasCapability("a"));
Assert.That(settings.GetCapability("a"), Is.TypeOf<int>().And.EqualTo(123));

settings.AddMetadataSetting("a", 123f);
Assert.That(settings.HasCapability("a"));
Assert.That(settings.GetCapability("a"), Is.TypeOf<float>().And.EqualTo(123f));

settings.AddMetadataSetting("a", 123d);
Assert.That(settings.HasCapability("a"));
Assert.That(settings.GetCapability("a"), Is.TypeOf<double>().And.EqualTo(123d));

JsonNode trueName = JsonValue.Create(true);
settings.AddMetadataSetting("a", trueName);
Assert.That(settings.HasCapability("a"));
Assert.That(settings.GetCapability("a"), Is.InstanceOf<JsonNode>().And.EqualTo(trueName).Using<JsonNode>(JsonNode.DeepEquals));

var reader = new Utf8JsonReader("false"u8);
JsonElement trueElement = JsonElement.ParseValue(ref reader);

settings.AddMetadataSetting("a", trueElement);
Assert.That(settings.HasCapability("a"));
Assert.That(settings.GetCapability("a"), Is.TypeOf<JsonElement>().And.Matches<JsonElement>(static left =>
{
return left.ValueKind == JsonValueKind.False;
}));

List<int> intValues = [1, 2, 3];
settings.AddMetadataSetting("a", intValues);
Assert.That(settings.HasCapability("a"));
Assert.That(settings.GetCapability("a"), Is.TypeOf<List<int>>().And.EqualTo(intValues));

Dictionary<string, int> dictionaryValues = new Dictionary<string, int>
{
{"value1", 1 },
{"value2", 1 },
};
settings.AddMetadataSetting("a", dictionaryValues);
Assert.That(settings.HasCapability("a"));
Assert.That(settings.GetCapability("a"), Is.TypeOf<Dictionary<string, int>>().And.EqualTo(dictionaryValues));
}
}
}
Loading