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
69 changes: 35 additions & 34 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 @@ -146,7 +141,7 @@ 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))
if (!IsJsonSerializable(settingValue))
{
throw new ArgumentException("Metadata setting value must be JSON serializable.", nameof(settingValue));
}
Expand All @@ -161,9 +156,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 +193,7 @@ public void SetMustMatchDriverOptions(DriverOptions options)
}
}

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

/// <summary>
Expand Down Expand Up @@ -256,7 +251,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,12 +287,12 @@ 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());
Expand All @@ -306,34 +301,42 @@ private List<object> GetFirstMatchOptionsAsSerializableList()
return optionsMatches;
}

private bool IsJsonSerializable(object arg)
private static bool IsJsonSerializable(object arg)
nvborisenko marked this conversation as resolved.
Show resolved Hide resolved
{
IEnumerable argAsEnumerable = arg as IEnumerable;
IDictionary argAsDictionary = arg as IDictionary;
if (arg is null)
{
return true;
}

if (arg is string || arg is float || arg is double || arg is int || arg is long || arg is bool || arg == null)
if (arg is string or float or double or int or long or bool)
{
return true;
}
else if (argAsDictionary != null)

if (arg is JsonNode or JsonElement)
{
foreach (object key in argAsDictionary.Keys)
return true;
}

if (arg is IDictionary argAsDictionary)
{
foreach (DictionaryEntry item in argAsDictionary)
{
if (!(key is string))
if (item.Key is not string)
{
return false;
}
}

foreach (object value in argAsDictionary.Values)
{
if (!IsJsonSerializable(value))
if (!IsJsonSerializable(item.Value))
{
return false;
}
}

return true;
}
else if (argAsEnumerable != null)

if (arg is IEnumerable argAsEnumerable)
{
foreach (object item in argAsEnumerable)
{
Expand All @@ -342,13 +345,11 @@ private bool IsJsonSerializable(object arg)
return false;
}
}
}
else
{
return false;

return true;
}

return true;
return false;
}
}
}
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