diff --git a/CSF.Screenplay.Selenium/CSF.Screenplay.Selenium.csproj b/CSF.Screenplay.Selenium/CSF.Screenplay.Selenium.csproj
index 743e2d55..a7318f27 100644
--- a/CSF.Screenplay.Selenium/CSF.Screenplay.Selenium.csproj
+++ b/CSF.Screenplay.Selenium/CSF.Screenplay.Selenium.csproj
@@ -4,6 +4,12 @@
+
+ $(TargetFrameworks);net6.0
+ $(DefineConstants);RECORD_STRUCT_SUPPORT
+
+
+
CSF.Screenplay.Selenium
$(MSBuildProjectDirectory)\bin\$(Configuration)\$(TargetFramework)\$(AssemblyName).xml
diff --git a/CSF.Screenplay.Selenium/Color.cs b/CSF.Screenplay.Selenium/Color.cs
new file mode 100644
index 00000000..9a031ca3
--- /dev/null
+++ b/CSF.Screenplay.Selenium/Color.cs
@@ -0,0 +1,445 @@
+using System;
+using SysColor = System.Drawing.Color;
+
+namespace CSF.Screenplay.Selenium
+{
+ ///
+ /// Immutable type represents a color on the web, in a manner which may be converted and compared between web-supported formats.
+ ///
+ ///
+ ///
+ /// An uninitialized instance of this type will represent "fully-transparent black". This is equivalent to the value available at
+ /// .
+ /// Internally, this type stores color as three unsigned values (Red, Green, Blue) and a
+ /// representing the transparency (Alpha). The Alpha value must be between 0 (fully transparent)
+ /// and 1 (fully opaque).
+ ///
+ ///
+ /// For a list of the well-known named colors, such as green or rosybrown, see the class.
+ /// This has static properties to get instances of for each of these.
+ /// It also has methods to get a color based upon a string color name.
+ ///
+ ///
+ /// This struct contains functionality to compare equality with, and convert to/from the .NET built-in .
+ /// It also contains parsing logic to parse/format an instance from/to string representations of color which are used by web browsers.
+ /// See the MDN writeup of web color for
+ /// more information about the valid formats.
+ ///
+ ///
+ /// When using a sufficiently high version of .NET & the C# language (.NET 6 or higher), this type is a readonly record struct.
+ /// This offers improved and extended functionality, such as access to
+ /// non-destructive
+ /// mutation using the with keyword. In lower .NET/C# versions, this type is a plain struct with functionality
+ /// supported by that language version.
+ ///
+ ///
+#if RECORD_STRUCT_SUPPORT
+ public readonly record struct Color
+#else
+ public struct Color
+#endif
+ : IEquatable, IEquatable, IEquatable
+ {
+ readonly double alpha;
+
+ ///
+ /// Gets the red component in the sRGB color space.
+ ///
+#if RECORD_STRUCT_SUPPORT
+ public readonly byte Red { get; init; }
+#else
+ public byte Red { get; private set; }
+#endif
+
+ ///
+ /// Gets the green component in the sRGB color space.
+ ///
+#if RECORD_STRUCT_SUPPORT
+ public readonly byte Green { get; init; }
+#else
+ public byte Green { get; private set; }
+#endif
+
+ ///
+ /// Gets the blue component in the sRGB color space.
+ ///
+#if RECORD_STRUCT_SUPPORT
+ public readonly byte Blue { get; init; }
+#else
+ public byte Blue { get; private set; }
+#endif
+
+ ///
+ /// Gets the alpha (transparency) component in the sRGB color space.
+ ///
+ ///
+ /// The value of this property will always be between zero (fully transparent) and one (fully opaque).
+ ///
+#if RECORD_STRUCT_SUPPORT
+ public readonly double Alpha
+ {
+ get => alpha;
+ init => this.alpha = SanitizeAlpha(value);
+ }
+#else
+ public double Alpha { get => alpha; }
+#endif
+
+ ///
+ /// Gets a representation of , as a byte.
+ ///
+ public byte AlphaAsByte => (byte) Math.Floor(Math.Round(Alpha * 255, 0, MidpointRounding.AwayFromZero));
+
+ ///
+ /// Converts this color to a .
+ ///
+ ///
+ ///
+ /// In some situations, it might be useful to convert to/from the .NET built-in
+ /// type. Use this method and to do so.
+ ///
+ ///
+ /// A representation of this color.
+ public SysColor ToSystemDrawingColor() => SysColor.FromArgb(AlphaAsByte, Red, Green, Blue);
+
+ ///
+ public bool Equals(SysColor other) => ToSystemDrawingColor().Equals(other);
+
+ ///
+ public bool Equals(Color other) => other.Red == Red
+ && other.Green == Green
+ && other.Blue == Blue
+ && other.AlphaAsByte == AlphaAsByte;
+
+ ///
+ public bool Equals(string other) => TryParse(other, out var color) && Equals(color);
+
+#if !RECORD_STRUCT_SUPPORT
+ ///
+ public override bool Equals(object obj)
+ {
+ if(obj is Color color) return Equals(color);
+ if(obj is SysColor sysColor) return Equals(sysColor);
+ if(obj is string strColor) return Equals(strColor);
+ return false;
+ }
+#endif
+
+ ///
+ /// Gets a representation of the current instance in the format rgb(RRR, GGG, BBB).
+ ///
+ ///
+ ///
+ /// The characters RRR, GGG and BBB in the format example correpsond to three decimal
+ /// unsigned byte values (0 to 255) which indicate the red, green and blue components of the color respectively.
+ ///
+ ///
+ /// Note that this format omits the alpha (transparency) component of the color. Thus, the resulting string
+ /// will not be equal to the value which created it if the alpha component is not equal to 1.
+ /// The only string-formatting methods which preserve all of the information in the color instance are
+ /// and the default method, which both return equivalent results.
+ ///
+ ///
+ /// An RGB-format string.
+ public string ToRgbString() => $"rgb({Red}, {Green}, {Blue})";
+
+ ///
+ /// Gets a representation of the current instance in the format rgba(RRR, GGG, BBB, A).
+ ///
+ ///
+ ///
+ /// The characters RRR, GGG and BBB in the format example correpsond to three decimal
+ /// unsigned byte values (0 to 255) which indicate the red, green and blue components of the color respectively.
+ /// The character A corresponds to the alpha (transparency) component of the color. It will be either
+ /// the numbers 1 or 0, or it will be a decimal number between one and zero.
+ ///
+ ///
+ /// Out of the string-formatting methods available on the type, only this one and the default
+ /// method preserve all information in the color instance. Thus this method may be used
+ /// alongside or to reliably round-trip an RGBA
+ /// string color representation.
+ ///
+ ///
+ /// An RGBA-format string.
+ public string ToRgbaString() => $"rgba({Red}, {Green}, {Blue}, {Alpha})";
+
+ ///
+ /// Gets a representation of the current instance in the format #RRGGBB.
+ ///
+ ///
+ ///
+ /// The characters RR, GG and BB in the format example correpsond to three hexadecimal
+ /// unsigned byte values (00 to FF, corresponding to decimal 0 to 255). These indicate the red, green and
+ /// blue components of the color respectively.
+ ///
+ ///
+ /// Note that this format omits the alpha (transparency) component of the color. Thus, the resulting string
+ /// will not be equal to the value which created it if the alpha component is not equal to 1.
+ /// The only string-formatting methods which preserve all of the information in the color instance are
+ /// and the default method, which both return equivalent results.
+ ///
+ ///
+ /// An RGB-format string.
+ public string ToHexString() => $"#{Red:X}{Green:X}{Blue:X}";
+
+ ///
+ /// Returns a string in the same format as .
+ ///
+ ///
+ /// This method and are equivalent.
+ ///
+ /// An RGBA-format string.
+ public override string ToString() => ToRgbaString();
+
+ ///
+ public override int GetHashCode() => HashCode.Combine(Red, Green, Blue, AlphaAsByte);
+
+ ///
+ /// Creates a new instance of from the specified RGBA values.
+ ///
+ ///
+ ///
+ /// The byte data type for the R, G and B components of the color ensure that it is impossible to specify
+ /// values outside the permitted range. As for the , if a value of less than zero is specified
+ /// here then it is treated as zero. Likewise if a value of greater than one is specified then it will be treated as one.
+ ///
+ ///
+ /// The red component in the sRGB color space
+ /// The green component in the sRGB color space
+ /// The blue component in the sRGB color space
+ /// The alpha (transparency) component, which must be between zero and one.
+ public Color(byte red, byte green, byte blue, double alpha = 1D)
+ {
+ Red = red;
+ Green = green;
+ Blue = blue;
+ this.alpha = SanitizeAlpha(alpha);
+ }
+
+ ///
+ /// Sanitizes a value intended for .
+ ///
+ ///
+ /// If is less than zero then this method returns zero.
+ /// If is more than one then this method returns one.
+ /// Otherwise, this method returns .
+ ///
+ /// The alpha value.
+ /// A sanitized alpha value.
+ static double SanitizeAlpha(double alpha) => alpha < 0 ? 0 : alpha > 1 ? 1 : alpha;
+
+ ///
+ /// Converts a into a .
+ ///
+ ///
+ ///
+ /// In some situations, it might be useful to convert to/from the .NET built-in
+ /// type. Use this method and to do so.
+ ///
+ ///
+ /// A representation of the specified color.
+ public static Color FromSystemDrawingColour(SysColor color)
+ => new Color(color.R, color.G, color.B, color.A / 255);
+
+ ///
+ /// Gets a value indicating whether the specified is a valid string representation
+ /// for a . If this returns then exposes the parsed
+ /// value.
+ ///
+ ///
+ ///
+ /// If this method returns then the output parameter will contain
+ /// the color parsed from the specified string. If this method returns then the output parameter
+ /// has an undefined value and must not be used.
+ ///
+ /// This method will not raise an exception and it is safe to use with unknown strings, including those which
+ /// might be .
+ ///
+ /// A string containing a color value which is compatible with the web.
+ /// See the MDN writeup on
+ /// web colors for more information.
+ /// If this method returns , then this parameter exposes the parsed color value.
+ /// If this method returns then the value of this parameter must be ignored.
+ /// if parsing is successful (the contains a valid &
+ /// recognized color representation); if not
+ public static bool TryParse(string webColorValue, out Color color)
+ {
+ color = default;
+ if(string.IsNullOrWhiteSpace(webColorValue)) return false;
+
+ var allParsers = new IParsesColor[]
+ {
+ new RgbColorParser(),
+ new RgbPercentageColorParser(),
+ new RgbaColorParser(),
+ new RgbaPercentageColorParser(),
+ new Hex6ColorParser(),
+ new Hex3ColorParser(),
+ new HslColorParser(),
+ new HslaColorParser(),
+ new NamedColorParser(),
+ };
+
+ foreach(var parser in allParsers)
+ if(parser.TryParseColor(webColorValue, out color)) return true;
+
+ color = default;
+ return false;
+ }
+
+ ///
+ /// Parses a from a string representation of a color.
+ ///
+ ///
+ ///
+ /// If is a valid representation of a web-compatible color value then this method will
+ /// return that color as an instance of . If not, this method will throw an exception.
+ ///
+ /// If you are not certain that the represents a valid colour then use
+ /// instead.
+ ///
+ /// A string containing a color value which is compatible with the web.
+ /// See the MDN writeup on
+ /// web colors for more information.
+ /// The parsed color.
+ /// If is .
+ /// If is not a recognized representation of a color.
+ public static Color Parse(string webColorValue)
+ {
+ if (webColorValue is null)
+ throw new ArgumentNullException(nameof(webColorValue));
+
+ return TryParse(webColorValue, out var color)
+ ? color
+ : throw new FormatException("The string color representation must be a in a web-compatible recognized format.");
+ }
+
+ ///
+ /// Gets a value indicating whether the specified and
+ /// are equal.
+ ///
+ ///
+ /// Despite the type differences, two colors are equal if they represent the same sRGB color.
+ ///
+ /// The
+ /// The
+ /// if the two different colors represent the same sRGB color; if not.
+ public static bool operator ==(Color color, SysColor sysColor) => color.Equals(sysColor);
+
+ ///
+ /// Gets a value indicating whether the specified and
+ /// are not equal.
+ ///
+ ///
+ /// Despite the type differences, two colors are equal if they represent the same sRGB color.
+ ///
+ /// The
+ /// The
+ /// if the two different colors represent the same sRGB color; if not.
+ public static bool operator !=(Color color, SysColor sysColor) => !(color == sysColor);
+
+ ///
+ /// Gets a value indicating whether the specified and
+ /// are equal.
+ ///
+ ///
+ /// Despite the type differences, two colors are equal if they represent the same sRGB color.
+ ///
+ /// The
+ /// The
+ /// if the two different colors represent the same sRGB color; if not.
+ public static bool operator ==(SysColor sysColor, Color color) => color.Equals(sysColor);
+
+ ///
+ /// Gets a value indicating whether the specified and
+ /// are not equal.
+ ///
+ ///
+ /// Despite the type differences, two colors are equal if they represent the same sRGB color.
+ ///
+ /// The
+ /// The
+ /// if the two different colors represent the same sRGB color; if not.
+ public static bool operator !=(SysColor sysColor, Color color) => !(color == sysColor);
+
+ ///
+ /// Gets a value indicating whether the specified and representation of a color
+ /// are equal.
+ ///
+ ///
+ /// A color is equal to a string if the result of is
+ /// and if the resulting parsed instance is equal to the current color.
+ /// Note that this means that a color is never equal to a string which is not a valid color representation, or which is .
+ ///
+ /// The
+ /// The string which represents a color
+ /// if the current color and the string color representation indicate the same sRGB color; if not.
+ public static bool operator ==(Color color, string stringColor) => color.Equals(stringColor);
+
+ ///
+ /// Gets a value indicating whether the specified and representation of a color
+ /// are not equal.
+ ///
+ ///
+ /// A color is equal to a string if the result of is
+ /// and if the resulting parsed instance is equal to the current color.
+ /// Note that this means that a color is never equal to a string which is not a valid color representation, or which is .
+ ///
+ /// The
+ /// The string which represents a color
+ /// if the current color and the string color representation indicate the same sRGB color; if not.
+ public static bool operator !=(Color color, string stringColor) => !(color == stringColor);
+
+ ///
+ /// Gets a value indicating whether the specified and representation of a color
+ /// are equal.
+ ///
+ ///
+ /// A color is equal to a string if the result of is
+ /// and if the resulting parsed instance is equal to the current color.
+ /// Note that this means that a color is never equal to a string which is not a valid color representation, or which is .
+ ///
+ /// The
+ /// The string which represents a color
+ /// if the current color and the string color representation indicate the same sRGB color; if not.
+ public static bool operator ==(string stringColor, Color color) => color.Equals(stringColor);
+
+ ///
+ /// Gets a value indicating whether the specified and representation of a color
+ /// are not equal.
+ ///
+ ///
+ /// A color is equal to a string if the result of is
+ /// and if the resulting parsed instance is equal to the current color.
+ /// Note that this means that a color is never equal to a string which is not a valid color representation, or which is .
+ ///
+ /// The
+ /// The string which represents a color
+ /// if the current color and the string color representation indicate the same sRGB color; if not.
+ public static bool operator !=(string stringColor, Color color) => !(color == stringColor);
+
+ #if !RECORD_STRUCT_SUPPORT
+ ///
+ /// Gets a value indicating whether the two specified instances are equal.
+ ///
+ ///
+ /// Two colors are equal if they represent the same sRGB color.
+ ///
+ /// The first
+ /// The second
+ /// if the two colors represent the same sRGB color; if not.
+ public static bool operator ==(Color first, Color second) => first.Equals(second);
+
+ ///
+ /// Gets a value indicating whether the two specified instances are not equal.
+ ///
+ ///
+ /// Two colors are equal if they represent the same sRGB color.
+ ///
+ /// The first
+ /// The second
+ /// if the two colors represent the same sRGB color; if not.
+ public static bool operator !=(Color first, Color second) => !(first == second);
+#endif
+ }
+}
\ No newline at end of file
diff --git a/CSF.Screenplay.Selenium/ColorParser.cs b/CSF.Screenplay.Selenium/ColorParser.cs
new file mode 100644
index 00000000..b75f4f2b
--- /dev/null
+++ b/CSF.Screenplay.Selenium/ColorParser.cs
@@ -0,0 +1,243 @@
+using System;
+using System.Text.RegularExpressions;
+
+namespace CSF.Screenplay.Selenium
+{
+ ///
+ /// An object which can get an instance of .
+ ///
+ internal interface IParsesColor
+ {
+ ///
+ /// Attempts to parse an instance of from the specified .
+ ///
+ ///
+ ///
+ /// If the parsing is successful then this method will return and the
+ /// will contain the parsed color value.
+ /// Otherwise it will return and the parameter must be ignored.
+ ///
+ ///
+ /// A string representing a color.
+ /// If this method returns then this parameter exposes the parsed
+ /// color; if not then this value must be ignored.
+ /// if the parsing was a success; if not.
+ bool TryParseColor(string colorValue, out Color color);
+ }
+
+ internal abstract class NumericColorParser : IParsesColor
+ {
+ protected static readonly string
+ BytePart = @"\s*(\d{1,3})\s*",
+ DecimalAlphaPart = @"\s*(0|1|0\.\d+)\s*",
+ PercentDecimalPart = @"\s*(\d{1,3}|\d{1,2}\.\d+)%\s*",
+ PercentPart = @"\s*(\d{1,3})%\s*",
+ Hex2Part = @"([0-9a-f]{2})",
+ Hex1Part = @"([0-9a-f])";
+
+ ///
+ public virtual bool TryParseColor(string colorValue, out Color color)
+ {
+ color = default;
+ var match = GetParsingRegex(out var includesAlpha).Match(colorValue);
+ if(!match.Success) return false;
+
+ color = new Color(GetByte(match.Groups[1]),
+ GetByte(match.Groups[2]),
+ GetByte(match.Groups[3]),
+ includesAlpha ? double.Parse(match.Groups[4].Value) : 1);
+ return true;
+ }
+
+ ///
+ /// Gets a regex which parses a string color representation.
+ ///
+ /// if this regex includes a value for the alpha channel; if not.
+ /// A regex
+ protected virtual Regex GetParsingRegex(out bool includesAlpha)
+ => new Regex(GetParsingPattern(out includesAlpha),
+ RegexOptions.CultureInvariant | RegexOptions.Compiled | RegexOptions.IgnoreCase,
+ TimeSpan.FromMilliseconds(500));
+
+ ///
+ /// Gets a regex pattern which parses a string color representation.
+ ///
+ /// if this regex includes a value for the alpha channel; if not.
+ /// A regex
+ protected abstract string GetParsingPattern(out bool includesAlpha);
+
+ ///
+ /// Parses a byte value from the value of the match group.
+ ///
+ /// A regex match group
+ /// A byte value
+ protected virtual byte GetByte(Group matchGroup) => byte.Parse(matchGroup.Value);
+ }
+
+ internal class RgbColorParser : NumericColorParser
+ {
+ ///
+ protected override string GetParsingPattern(out bool includesAlpha)
+ {
+ includesAlpha = false;
+ return $"^\\s*rgb\\({BytePart},{BytePart},{BytePart}\\)\\s*$";
+ }
+ }
+
+ internal class RgbaColorParser : NumericColorParser
+ {
+ ///
+ protected override string GetParsingPattern(out bool includesAlpha)
+ {
+ includesAlpha = true;
+ return $"^\\s*rgba\\({BytePart},{BytePart},{BytePart},{DecimalAlphaPart}\\)\\s*$";
+ }
+ }
+
+ internal class RgbPercentageColorParser : NumericColorParser
+ {
+ ///
+ protected override string GetParsingPattern(out bool includesAlpha)
+ {
+ includesAlpha = false;
+ return $"^\\s*rgb\\({PercentDecimalPart},{PercentDecimalPart},{PercentDecimalPart}\\)\\s*$";
+ }
+
+ ///
+ protected override byte GetByte(Group matchGroup) => (byte) (double.Parse(matchGroup.Value) / 100D * 255D);
+ }
+
+ internal class RgbaPercentageColorParser : RgbPercentageColorParser
+ {
+ ///
+ protected override string GetParsingPattern(out bool includesAlpha)
+ {
+ includesAlpha = true;
+ return $"^\\s*rgba\\({PercentDecimalPart},{PercentDecimalPart},{PercentDecimalPart},{DecimalAlphaPart}\\)\\s*$";
+ }
+ }
+
+ internal class Hex3ColorParser : NumericColorParser
+ {
+ ///
+ protected override string GetParsingPattern(out bool includesAlpha)
+ {
+ includesAlpha = false;
+ return $"^\\s*#{Hex1Part}{Hex1Part}{Hex1Part}\\s*$";
+ }
+
+ ///
+ protected override byte GetByte(Group matchGroup) => Convert.ToByte(matchGroup.Value + matchGroup.Value, 16);
+ }
+
+ internal class Hex6ColorParser : NumericColorParser
+ {
+ ///
+ protected override string GetParsingPattern(out bool includesAlpha)
+ {
+ includesAlpha = false;
+ return $"^\\s*#{Hex2Part}{Hex2Part}{Hex2Part}\\s*$";
+ }
+
+ ///
+ protected override byte GetByte(Group matchGroup) => Convert.ToByte(matchGroup.Value, 16);
+ }
+
+ internal class HslColorParser : NumericColorParser
+ {
+ ///
+ public override bool TryParseColor(string colorValue, out Color color)
+ {
+ color = default;
+ var match = GetParsingRegex(out var includesAlpha).Match(colorValue);
+ if(!match.Success) return false;
+
+ color = HslToRgb(short.Parse(match.Groups[1].Value),
+ byte.Parse(match.Groups[2].Value),
+ byte.Parse(match.Groups[3].Value),
+ includesAlpha ? double.Parse(match.Groups[4].Value) : 1);
+ return true;
+ }
+
+ ///
+ protected override string GetParsingPattern(out bool includesAlpha)
+ {
+ includesAlpha = false;
+ return $"^\\s*hsl\\({BytePart},{PercentPart},{PercentPart}\\)\\s*$";
+ }
+
+ ///
+ /// Converts an HSL color to the sRGB color space.
+ ///
+ ///
+ ///
+ /// This is based upon the algorithms available at
+ /// The
+ /// Selenium 4.40.0 HSL converter for Java.
+ ///
+ ///
+ /// The hue value in degrees (0 to 360)
+ /// The saturation percentage (0 to 100)
+ /// The lightness percentage (0 to 100)
+ /// The alpha (transparency) value (0 to 1)
+ /// An instance of in the sRGB color space
+ static Color HslToRgb(short h, byte s, byte l, double alpha)
+ {
+ var hue = h / 360D;
+ var saturation = s / 100D;
+ var lightness = l / 100D;
+
+ // FYI: Saturation = zero means gray, making hue irrelevant
+ if(s == 0)
+ {
+ var allChannels = (byte) Math.Round(lightness * 255D, MidpointRounding.AwayFromZero);
+ return new Color(allChannels, allChannels, allChannels, alpha);
+ }
+
+ var luminosity2 = (lightness < 0.5)
+ ? lightness * (saturation + 1D)
+ : lightness + saturation - lightness * saturation;
+ var luminosity1 = 2D * lightness - luminosity2;
+
+ return new Color(HueValueToRgbByte(luminosity1, luminosity2, hue + 1D / 3D),
+ HueValueToRgbByte(luminosity1, luminosity2, hue),
+ HueValueToRgbByte(luminosity1, luminosity2, hue - 1D / 3D),
+ alpha);
+ }
+
+ static byte HueValueToRgbByte(double luminosity1,
+ double luminosity2,
+ double hueValue)
+ => (byte) Math.Round(HueValueToRgb(luminosity1, luminosity2, hueValue) * 255D, MidpointRounding.AwayFromZero);
+
+ static double HueValueToRgb(double luminosity1,
+ double luminosity2,
+ double hueValue)
+ {
+ if (hueValue < 0D) hueValue += 1;
+ if (hueValue > 1D) hueValue -= 1.0;
+ if (hueValue < 1D / 6D) return luminosity1 + (luminosity2 - luminosity1) * 6D * hueValue;
+ if (hueValue < 1D / 2D) return luminosity2;
+ if (hueValue < 2D / 3D) return luminosity1 + (luminosity2 - luminosity1) * (2D / 3D - hueValue) * 6D;
+ return luminosity1;
+ }
+ }
+
+ internal class HslaColorParser : HslColorParser
+ {
+ ///
+ protected override string GetParsingPattern(out bool includesAlpha)
+ {
+ includesAlpha = true;
+ return $"^\\s*hsla\\({BytePart},{PercentPart},{PercentPart},{DecimalAlphaPart}\\)\\s*$";
+ }
+ }
+
+ internal class NamedColorParser : IParsesColor
+ {
+ ///
+ public bool TryParseColor(string colorValue, out Color color)
+ => Colors.TryGetNamedColor(colorValue, out color);
+ }
+
+}
\ No newline at end of file
diff --git a/CSF.Screenplay.Selenium/Colors.cs b/CSF.Screenplay.Selenium/Colors.cs
new file mode 100644
index 00000000..344a0fd8
--- /dev/null
+++ b/CSF.Screenplay.Selenium/Colors.cs
@@ -0,0 +1,243 @@
+using System;
+using System.Collections.ObjectModel;
+using System.Linq;
+using System.Reflection;
+
+namespace CSF.Screenplay.Selenium
+{
+ ///
+ /// Static type containing the pre-defined colors for the web.
+ ///
+ ///
+ ///
+ /// Each static property of this type corresponds to a well-known pre-defined color, according to
+ /// the W3C HTML4 spec.
+ ///
+ ///
+ /// Alternatively, to get a instance which matches a string which names a color (case insensitive)
+ /// use either or .
+ ///
+ ///
+ public static class Colors
+ {
+ ///
+ /// Caches the named colors and their names, for performance reasons when using and/or
+ /// .
+ ///
+ static readonly ReadOnlyDictionary namedColors = GetNamedColorCache();
+
+#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
+ public static Color TRANSPARENT => new Color(0, 0, 0, 0);
+ public static Color ALICEBLUE => new Color(240, 248, 255, 1);
+ public static Color ANTIQUEWHITE => new Color(250, 235, 215, 1);
+ public static Color AQUA => new Color(0, 255, 255, 1);
+ public static Color AQUAMARINE => new Color(127, 255, 212, 1);
+ public static Color AZURE => new Color(240, 255, 255, 1);
+ public static Color BEIGE => new Color(245, 245, 220, 1);
+ public static Color BISQUE => new Color(255, 228, 196, 1);
+ public static Color BLACK => new Color(0, 0, 0, 1);
+ public static Color BLANCHEDALMOND => new Color(255, 235, 205, 1);
+ public static Color BLUE => new Color(0, 0, 255, 1);
+ public static Color BLUEVIOLET => new Color(138, 43, 226, 1);
+ public static Color BROWN => new Color(165, 42, 42, 1);
+ public static Color BURLYWOOD => new Color(222, 184, 135, 1);
+ public static Color CADETBLUE => new Color(95, 158, 160, 1);
+ public static Color CHARTREUSE => new Color(127, 255, 0, 1);
+ public static Color CHOCOLATE => new Color(210, 105, 30, 1);
+ public static Color CORAL => new Color(255, 127, 80, 1);
+ public static Color CORNFLOWERBLUE => new Color(100, 149, 237, 1);
+ public static Color CORNSILK => new Color(255, 248, 220, 1);
+ public static Color CRIMSON => new Color(220, 20, 60, 1);
+ public static Color CYAN => new Color(0, 255, 255, 1);
+ public static Color DARKBLUE => new Color(0, 0, 139, 1);
+ public static Color DARKCYAN => new Color(0, 139, 139, 1);
+ public static Color DARKGOLDENROD => new Color(184, 134, 11, 1);
+ public static Color DARKGRAY => new Color(169, 169, 169, 1);
+ public static Color DARKGREEN => new Color(0, 100, 0, 1);
+ public static Color DARKGREY => new Color(169, 169, 169, 1);
+ public static Color DARKKHAKI => new Color(189, 183, 107, 1);
+ public static Color DARKMAGENTA => new Color(139, 0, 139, 1);
+ public static Color DARKOLIVEGREEN => new Color(85, 107, 47, 1);
+ public static Color DARKORANGE => new Color(255, 140, 0, 1);
+ public static Color DARKORCHID => new Color(153, 50, 204, 1);
+ public static Color DARKRED => new Color(139, 0, 0, 1);
+ public static Color DARKSALMON => new Color(233, 150, 122, 1);
+ public static Color DARKSEAGREEN => new Color(143, 188, 143, 1);
+ public static Color DARKSLATEBLUE => new Color(72, 61, 139, 1);
+ public static Color DARKSLATEGRAY => new Color(47, 79, 79, 1);
+ public static Color DARKSLATEGREY => new Color(47, 79, 79, 1);
+ public static Color DARKTURQUOISE => new Color(0, 206, 209, 1);
+ public static Color DARKVIOLET => new Color(148, 0, 211, 1);
+ public static Color DEEPPINK => new Color(255, 20, 147, 1);
+ public static Color DEEPSKYBLUE => new Color(0, 191, 255, 1);
+ public static Color DIMGRAY => new Color(105, 105, 105, 1);
+ public static Color DIMGREY => new Color(105, 105, 105, 1);
+ public static Color DODGERBLUE => new Color(30, 144, 255, 1);
+ public static Color FIREBRICK => new Color(178, 34, 34, 1);
+ public static Color FLORALWHITE => new Color(255, 250, 240, 1);
+ public static Color FORESTGREEN => new Color(34, 139, 34, 1);
+ public static Color FUCHSIA => new Color(255, 0, 255, 1);
+ public static Color GAINSBORO => new Color(220, 220, 220, 1);
+ public static Color GHOSTWHITE => new Color(248, 248, 255, 1);
+ public static Color GOLD => new Color(255, 215, 0, 1);
+ public static Color GOLDENROD => new Color(218, 165, 32, 1);
+ public static Color GRAY => new Color(128, 128, 128, 1);
+ public static Color GREY => new Color(128, 128, 128, 1);
+ public static Color GREEN => new Color(0, 128, 0, 1);
+ public static Color GREENYELLOW => new Color(173, 255, 47, 1);
+ public static Color HONEYDEW => new Color(240, 255, 240, 1);
+ public static Color HOTPINK => new Color(255, 105, 180, 1);
+ public static Color INDIANRED => new Color(205, 92, 92, 1);
+ public static Color INDIGO => new Color(75, 0, 130, 1);
+ public static Color IVORY => new Color(255, 255, 240, 1);
+ public static Color KHAKI => new Color(240, 230, 140, 1);
+ public static Color LAVENDER => new Color(230, 230, 250, 1);
+ public static Color LAVENDERBLUSH => new Color(255, 240, 245, 1);
+ public static Color LAWNGREEN => new Color(124, 252, 0, 1);
+ public static Color LEMONCHIFFON => new Color(255, 250, 205, 1);
+ public static Color LIGHTBLUE => new Color(173, 216, 230, 1);
+ public static Color LIGHTCORAL => new Color(240, 128, 128, 1);
+ public static Color LIGHTCYAN => new Color(224, 255, 255, 1);
+ public static Color LIGHTGOLDENRODYELLOW => new Color(250, 250, 210, 1);
+ public static Color LIGHTGRAY => new Color(211, 211, 211, 1);
+ public static Color LIGHTGREEN => new Color(144, 238, 144, 1);
+ public static Color LIGHTGREY => new Color(211, 211, 211, 1);
+ public static Color LIGHTPINK => new Color(255, 182, 193, 1);
+ public static Color LIGHTSALMON => new Color(255, 160, 122, 1);
+ public static Color LIGHTSEAGREEN => new Color(32, 178, 170, 1);
+ public static Color LIGHTSKYBLUE => new Color(135, 206, 250, 1);
+ public static Color LIGHTSLATEGRAY => new Color(119, 136, 153, 1);
+ public static Color LIGHTSLATEGREY => new Color(119, 136, 153, 1);
+ public static Color LIGHTSTEELBLUE => new Color(176, 196, 222, 1);
+ public static Color LIGHTYELLOW => new Color(255, 255, 224, 1);
+ public static Color LIME => new Color(0, 255, 0, 1);
+ public static Color LIMEGREEN => new Color(50, 205, 50, 1);
+ public static Color LINEN => new Color(250, 240, 230, 1);
+ public static Color MAGENTA => new Color(255, 0, 255, 1);
+ public static Color MAROON => new Color(128, 0, 0, 1);
+ public static Color MEDIUMAQUAMARINE => new Color(102, 205, 170, 1);
+ public static Color MEDIUMBLUE => new Color(0, 0, 205, 1);
+ public static Color MEDIUMORCHID => new Color(186, 85, 211, 1);
+ public static Color MEDIUMPURPLE => new Color(147, 112, 219, 1);
+ public static Color MEDIUMSEAGREEN => new Color(60, 179, 113, 1);
+ public static Color MEDIUMSLATEBLUE => new Color(123, 104, 238, 1);
+ public static Color MEDIUMSPRINGGREEN => new Color(0, 250, 154, 1);
+ public static Color MEDIUMTURQUOISE => new Color(72, 209, 204, 1);
+ public static Color MEDIUMVIOLETRED => new Color(199, 21, 133, 1);
+ public static Color MIDNIGHTBLUE => new Color(25, 25, 112, 1);
+ public static Color MINTCREAM => new Color(245, 255, 250, 1);
+ public static Color MISTYROSE => new Color(255, 228, 225, 1);
+ public static Color MOCCASIN => new Color(255, 228, 181, 1);
+ public static Color NAVAJOWHITE => new Color(255, 222, 173, 1);
+ public static Color NAVY => new Color(0, 0, 128, 1);
+ public static Color OLDLACE => new Color(253, 245, 230, 1);
+ public static Color OLIVE => new Color(128, 128, 0, 1);
+ public static Color OLIVEDRAB => new Color(107, 142, 35, 1);
+ public static Color ORANGE => new Color(255, 165, 0, 1);
+ public static Color ORANGERED => new Color(255, 69, 0, 1);
+ public static Color ORCHID => new Color(218, 112, 214, 1);
+ public static Color PALEGOLDENROD => new Color(238, 232, 170, 1);
+ public static Color PALEGREEN => new Color(152, 251, 152, 1);
+ public static Color PALETURQUOISE => new Color(175, 238, 238, 1);
+ public static Color PALEVIOLETRED => new Color(219, 112, 147, 1);
+ public static Color PAPAYAWHIP => new Color(255, 239, 213, 1);
+ public static Color PEACHPUFF => new Color(255, 218, 185, 1);
+ public static Color PERU => new Color(205, 133, 63, 1);
+ public static Color PINK => new Color(255, 192, 203, 1);
+ public static Color PLUM => new Color(221, 160, 221, 1);
+ public static Color POWDERBLUE => new Color(176, 224, 230, 1);
+ public static Color PURPLE => new Color(128, 0, 128, 1);
+ public static Color REBECCAPURPLE => new Color(102, 51, 153, 1);
+ public static Color RED => new Color(255, 0, 0, 1);
+ public static Color ROSYBROWN => new Color(188, 143, 143, 1);
+ public static Color ROYALBLUE => new Color(65, 105, 225, 1);
+ public static Color SADDLEBROWN => new Color(139, 69, 19, 1);
+ public static Color SALMON => new Color(250, 128, 114, 1);
+ public static Color SANDYBROWN => new Color(244, 164, 96, 1);
+ public static Color SEAGREEN => new Color(46, 139, 87, 1);
+ public static Color SEASHELL => new Color(255, 245, 238, 1);
+ public static Color SIENNA => new Color(160, 82, 45, 1);
+ public static Color SILVER => new Color(192, 192, 192, 1);
+ public static Color SKYBLUE => new Color(135, 206, 235, 1);
+ public static Color SLATEBLUE => new Color(106, 90, 205, 1);
+ public static Color SLATEGRAY => new Color(112, 128, 144, 1);
+ public static Color SLATEGREY => new Color(112, 128, 144, 1);
+ public static Color SNOW => new Color(255, 250, 250, 1);
+ public static Color SPRINGGREEN => new Color(0, 255, 127, 1);
+ public static Color STEELBLUE => new Color(70, 130, 180, 1);
+ public static Color TAN => new Color(210, 180, 140, 1);
+ public static Color TEAL => new Color(0, 128, 128, 1);
+ public static Color THISTLE => new Color(216, 191, 216, 1);
+ public static Color TOMATO => new Color(255, 99, 71, 1);
+ public static Color TURQUOISE => new Color(64, 224, 208, 1);
+ public static Color VIOLET => new Color(238, 130, 238, 1);
+ public static Color WHEAT => new Color(245, 222, 179, 1);
+ public static Color WHITE => new Color(255, 255, 255, 1);
+ public static Color WHITESMOKE => new Color(245, 245, 245, 1);
+ public static Color YELLOW => new Color(255, 255, 0, 1);
+ public static Color YELLOWGREEN => new Color(154, 205, 50, 1);
+ #pragma warning restore CS1591 // Missing XML comment for publicly visible type or member
+
+ ///
+ /// Gets a instance which matches the specified color-name.
+ ///
+ ///
+ ///
+ /// This method will throw an exception if does not correspond to a well-known color
+ /// name. This is a case-insensitive match.
+ /// If you are uncertain whether the is a well-known color name or not then consider
+ /// instead.
+ ///
+ ///
+ /// A named color (case insensitive)
+ /// A which corresponds to the specified named color.
+ /// If is
+ /// If does not correspond to a well-known named color
+ public static Color GetNamedColor(string colorName)
+ {
+ if (colorName is null) throw new ArgumentNullException(nameof(colorName));
+ if(namedColors.TryGetValue(colorName, out var color)) return color;
+ throw new ArgumentException($"'{colorName}' is not a well-known named color.");
+ }
+
+ ///
+ /// Gets a value indicating whether is a well-known color name.
+ /// Exposes a instance which corresponds to that name if it is.
+ ///
+ ///
+ ///
+ /// This method will never throw an exception for any value of .
+ /// Note that match with a well-known color name is performed in a case-insensitive manner.
+ ///
+ ///
+ /// A string which might correspond to a well-known color name.
+ /// If this method returns then this is a
+ /// which corresponds to the color indicated by ; if it returns
+ /// then this value is undefined and must be ignored.
+ /// if is a well-known color name; if not.
+ public static bool TryGetNamedColor(string colorName, out Color color)
+ {
+ color = default;
+ if (colorName is null) return false;
+ return namedColors.TryGetValue(colorName, out color);
+ }
+
+ ///
+ /// Gets a dictionary of the names/instances of the well-known colors.
+ ///
+ ///
+ ///
+ /// The keys of this dictionary are case insensitive and correspond to the names of the static properties of this type.
+ /// The values are instances of the that the corresponding property returns.
+ ///
+ ///
+ /// A cache of the named color values.
+ static ReadOnlyDictionary GetNamedColorCache()
+ {
+ var namedColorValues = typeof(Colors)
+ .GetProperties(BindingFlags.Static | BindingFlags.Public)
+ .ToDictionary(k => k.Name, v => (Color) v.GetValue(null), StringComparer.InvariantCultureIgnoreCase);
+ return new ReadOnlyDictionary(namedColorValues);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Tests/CSF.Screenplay.Selenium.Tests/Actions/ExecuteJavaScriptTests.cs b/Tests/CSF.Screenplay.Selenium.Tests/Actions/ExecuteJavaScriptTests.cs
index c7bd2617..a831a5d6 100644
--- a/Tests/CSF.Screenplay.Selenium.Tests/Actions/ExecuteJavaScriptTests.cs
+++ b/Tests/CSF.Screenplay.Selenium.Tests/Actions/ExecuteJavaScriptTests.cs
@@ -24,6 +24,6 @@ public async Task ExecuteJavaScriptShouldBeAbleToExecuteAScriptWithParameters(IS
await When(webster).AttemptsTo(ExecuteCustomScript(scriptBody).WithTheName("a script that changes the BG colour of an element").WithTheArguments("textContent", "#F00"));
var backgroundColor = await Then(webster).Should(ReadFromTheElement(textContent).TheCssProperty("background-color"));
- Assert.That(backgroundColor, Is.EqualTo("rgba(255, 0, 0, 1)"));
+ Assert.That(backgroundColor, Is.EqualTo(Colors.RED));
}
}
\ No newline at end of file
diff --git a/Tests/CSF.Screenplay.Selenium.Tests/Builders/QueryPredicatePrototypeBuilderTests.cs b/Tests/CSF.Screenplay.Selenium.Tests/Builders/QueryPredicatePrototypeBuilderTests.cs
index 637515a4..5ff3d6b1 100644
--- a/Tests/CSF.Screenplay.Selenium.Tests/Builders/QueryPredicatePrototypeBuilderTests.cs
+++ b/Tests/CSF.Screenplay.Selenium.Tests/Builders/QueryPredicatePrototypeBuilderTests.cs
@@ -175,7 +175,7 @@ public void CssPropertyShouldCreateASpecificationThatUsesGetCssValue(QueryPredic
.Setup(e => e.GetCssValue("color"))
.Returns("rgba(0, 0, 255, 1)");
- var sut = builder.CssProperty("color", v => v == "rgba(255, 0, 0, 1)").GetElementSpecification();
+ var sut = builder.CssProperty("color", v => v == Colors.RED).GetElementSpecification();
Assert.Multiple(() =>
{
@@ -196,7 +196,7 @@ public void CssPropertyWithValueShouldCreateASpecificationThatUsesGetCssValue(Qu
.Setup(e => e.GetCssValue("color"))
.Returns("rgba(0, 0, 255, 1)");
- var sut = builder.CssProperty("color", "rgba(255, 0, 0, 1)").GetElementSpecification();
+ var sut = builder.CssProperty("color", Colors.RED.ToRgbaString()).GetElementSpecification();
Assert.Multiple(() =>
{
diff --git a/Tests/CSF.Screenplay.Selenium.Tests/ColorTests.cs b/Tests/CSF.Screenplay.Selenium.Tests/ColorTests.cs
new file mode 100644
index 00000000..8684fe32
--- /dev/null
+++ b/Tests/CSF.Screenplay.Selenium.Tests/ColorTests.cs
@@ -0,0 +1,239 @@
+using System;
+
+namespace CSF.Screenplay.Selenium;
+
+[TestFixture, Parallelizable]
+public class ColorTests
+{
+ [TestCase("rgb(1, 2, 3)", 1, 2, 3, 1)]
+ [TestCase("rgb(10%, 20%, 30%)", 25, 51, 76, 1)]
+ [TestCase("rgb(\t1, 2, 3)", 1, 2, 3, 1)]
+ [TestCase("rgba(1, 2, 3, 0.5)", 1, 2, 3, 0.5D)]
+ [TestCase("rgba(10%, 20%, 30%, 0.5)", 25, 51, 76, 0.5D)]
+ [TestCase("#ff00a0", 255, 0, 160, 1)]
+ [TestCase("#01Ff03", 1, 255, 3, 1)]
+ [TestCase("#00FF33", 0, 255, 51, 1)]
+ [TestCase("#0f3", 0, 255, 51, 1)]
+ [TestCase("hsl(120, 100%, 25%)", 0, 128, 0, 1)]
+ [TestCase("hsl(100, 0%, 50%)", 128, 128, 128, 1)]
+ [TestCase("hsl(0, 100%, 50%)", 255, 0, 0, 1)]
+ [TestCase("hsl(120, 100%, 50%)", 0, 255, 0, 1)]
+ [TestCase("hsl(240, 100%, 50%)", 0, 0, 255, 1)]
+ [TestCase("hsl(0, 0%, 100%)", 255, 255, 255, 1)]
+ [TestCase("hsla(120, 100%, 25%, 1)", 0, 128, 0, 1)]
+ [TestCase("hsla(100, 0%, 50%, 0.5)", 128, 128, 128, 0.5)]
+ [TestCase("green", 0, 128, 0, 1)]
+ [TestCase("gray", 128, 128, 128, 1)]
+ [TestCase("transparent", 0, 0, 0, 0)]
+ public void TryParseShouldReturnTrueAndExposeACorrespondingColorForAValidString(string colorString, byte red, byte green, byte blue, double alpha)
+ {
+ var result = Color.TryParse(colorString, out var color);
+ Assert.Multiple(() =>
+ {
+ Assert.That(result, Is.True, "Parsing success");
+ Assert.That(color, Is.EqualTo(new Color(red, green, blue, alpha)), "Expected color matches");
+ });
+ }
+
+ [TestCase("rgb(1, 2, 3)", 1, 2, 3, 1)]
+ [TestCase("rgb(10%, 20%, 30%)", 25, 51, 76, 1)]
+ [TestCase("rgb(\t1, 2, 3)", 1, 2, 3, 1)]
+ [TestCase("rgba(1, 2, 3, 0.5)", 1, 2, 3, 0.5D)]
+ [TestCase("rgba(10%, 20%, 30%, 0.5)", 25, 51, 76, 0.5D)]
+ [TestCase("#ff00a0", 255, 0, 160, 1)]
+ [TestCase("#01Ff03", 1, 255, 3, 1)]
+ [TestCase("#00FF33", 0, 255, 51, 1)]
+ [TestCase("#0f3", 0, 255, 51, 1)]
+ [TestCase("hsl(120, 100%, 25%)", 0, 128, 0, 1)]
+ [TestCase("hsl(100, 0%, 50%)", 128, 128, 128, 1)]
+ [TestCase("hsl(0, 100%, 50%)", 255, 0, 0, 1)]
+ [TestCase("hsl(120, 100%, 50%)", 0, 255, 0, 1)]
+ [TestCase("hsl(240, 100%, 50%)", 0, 0, 255, 1)]
+ [TestCase("hsl(0, 0%, 100%)", 255, 255, 255, 1)]
+ [TestCase("hsla(120, 100%, 25%, 1)", 0, 128, 0, 1)]
+ [TestCase("hsla(100, 0%, 50%, 0.5)", 128, 128, 128, 0.5)]
+ [TestCase("green", 0, 128, 0, 1)]
+ [TestCase("gray", 128, 128, 128, 1)]
+ [TestCase("transparent", 0, 0, 0, 0)]
+ public void ParseShouldReturnTheCorrespondingColor(string colorString, byte red, byte green, byte blue, double alpha)
+ {
+ Assert.That(() => Color.Parse(colorString), Is.EqualTo(new Color(red, green, blue, alpha)));
+ }
+
+ [TestCase("elephants")]
+ [TestCase("!!!")]
+ [TestCase("😒")]
+ [TestCase(null)]
+ [TestCase("")]
+ [TestCase("#00z")]
+ [TestCase("rgb(1, 5, _)")]
+ public void TryParseShouldReturnFalseIfTheStringIsInvalid(string? invalidColor)
+ {
+ Assert.That(() => Color.TryParse(invalidColor, out _), Is.False);
+ }
+
+ [TestCase("elephants")]
+ [TestCase("!!!")]
+ [TestCase("😒")]
+ [TestCase("")]
+ [TestCase("#00z")]
+ [TestCase("rgb(1, 5, _)")]
+ public void ParseShouldThrowFormatExceptionIfTheStringIsInvalid(string invalidColor)
+ {
+ Assert.That(() => Color.Parse(invalidColor), Throws.InstanceOf());
+ }
+
+ [Test]
+ public void ParseShouldThrowAneIfTheStringIsNull()
+ {
+ Assert.That(() => Color.Parse(null), Throws.ArgumentNullException);
+ }
+
+ [TestCase("#f00", "rgb(255, 0, 0)")]
+ public void TwoColorsShouldBeEqualIfTheyAreRgbaEquivalent(string first, string second)
+ {
+ Assert.That(Color.Parse(first), Is.EqualTo(Color.Parse(second)));
+ }
+
+ [TestCase(255, 0, 0, "#FF0000")]
+ public void AColorShouldBeEqualToAStringIfTheyDescribeEquivalentColors(byte r, byte g, byte b, string other)
+ {
+ Assert.That(new Color(r, g, b), Is.EqualTo(other));
+ }
+
+ [TestCase("#f00", "rgb(255, 0, 1)")]
+ public void TwoColorsShouldBeNotEqualIfTheyAreNotRgbaEquivalent(string first, string second)
+ {
+ Assert.That(Color.Parse(first), Is.Not.EqualTo(Color.Parse(second)));
+ }
+
+ [TestCase(255, 0, 0, "#FF0001")]
+ public void AColorShouldNotBeEqualToAStringIfTheyDescribeDifferentColors(byte r, byte g, byte b, string other)
+ {
+ Assert.That(new Color(r, g, b), Is.Not.EqualTo(other));
+ }
+
+ [TestCase(255, 0, 0, 255, 0, 0)]
+ public void AColorShouldBeEqualToASystemColorIfTheyDescribeEquivalentColors(byte r, byte g, byte b, byte r2, byte g2, byte b2)
+ {
+ Assert.That(new Color(r, g, b), Is.EqualTo(System.Drawing.Color.FromArgb(r2, g2, b2)));
+ }
+
+ [TestCase(255, 0, 0, 255, 1, 0)]
+ public void TwoColorsShouldBeNotEqualIfTheyAreNotRgbaEquivalent(byte r, byte g, byte b, byte r2, byte g2, byte b2)
+ {
+ Assert.That(new Color(r, g, b), Is.Not.EqualTo(System.Drawing.Color.FromArgb(r2, g2, b2)));
+ }
+
+ [TestCase(0, 255, 0)]
+ public void FromSystemDrawingColourShouldReturnCorrectColorInstance(byte r, byte g, byte b)
+ {
+ Assert.That(Color.FromSystemDrawingColour(System.Drawing.Color.FromArgb(r, g, b)), Is.EqualTo(new Color(r, g, b)));
+ }
+
+ [TestCase("#f00", "rgb(255, 0, 0)")]
+ public void OpEqualityShouldReturnTrueForTwoColorsIfTheyAreRgbaEquivalent(string first, string second)
+ {
+ Assert.That(() => Color.Parse(first) == Color.Parse(second), Is.True);
+ }
+
+ [TestCase("#f00", "rgb(0, 255, 0)")]
+ public void OpEqualityShouldReturnFalseForTwoColorsIfTheyAreNotRgbaEquivalent(string first, string second)
+ {
+ Assert.That(() => Color.Parse(first) == Color.Parse(second), Is.False);
+ }
+
+ [TestCase("#f00", "rgb(0, 255, 0)")]
+ public void OpInequalityShouldReturnTrueForTwoColorsIfTheyAreNotRgbaEquivalent(string first, string second)
+ {
+ Assert.That(() => Color.Parse(first) != Color.Parse(second), Is.True);
+ }
+
+ [TestCase("#f00", "rgb(255, 0, 0)")]
+ public void OpEqualityShouldReturnTrueForAColorAndAStringIfTheyAreRgbaEquivalent(string first, string second)
+ {
+ Assert.That(() => Color.Parse(first) == second, Is.True);
+ }
+
+ [TestCase("#f00", "rgb(0, 255, 0)")]
+ public void OpEqualityShouldReturnFalseForAColorAndAStringIfTheyAreNotRgbaEquivalent(string first, string second)
+ {
+ Assert.That(() => Color.Parse(first) == second, Is.False);
+ }
+
+ [TestCase("#f00", "rgb(0, 255, 0)")]
+ public void OpInequalityShouldReturnTrueForAColorAndAStringIfTheyAreNotRgbaEquivalent(string first, string second)
+ {
+ Assert.That(() => Color.Parse(first) != second, Is.True);
+ }
+
+ [TestCase("#f00", 255, 0, 0)]
+ public void OpEqualityShouldReturnTrueForAColorAndASysColorIfTheyAreRgbaEquivalent(string first, byte r, byte g, byte b)
+ {
+ Assert.That(() => Color.Parse(first) == System.Drawing.Color.FromArgb(r, g, b), Is.True);
+ }
+
+ [TestCase("#f00", 0, 255, 0)]
+ public void OpEqualityShouldReturnFalseForAColorAndASysColorIfTheyAreNotRgbaEquivalent(string first, byte r, byte g, byte b)
+ {
+ Assert.That(() => Color.Parse(first) == System.Drawing.Color.FromArgb(r, g, b), Is.False);
+ }
+
+ [TestCase("#f00", 0, 255, 0)]
+ public void OpInequalityShouldReturnTrueForAColorAndASysColorIfTheyAreNotRgbaEquivalent(string first, byte r, byte g, byte b)
+ {
+ Assert.That(() => Color.Parse(first) != System.Drawing.Color.FromArgb(r, g, b), Is.True);
+ }
+
+ [TestCase("rgb(30, 128, 55)")]
+ public void ParseFollowedByToRgbStringShouldRoundtripTheColor(string colorString)
+ {
+ Assert.That(() => Color.Parse(colorString).ToRgbString(), Is.EqualTo(colorString));
+ }
+
+ [TestCase("rgba(30, 128, 55, 0.5)")]
+ [TestCase("rgba(30, 128, 55, 1)")]
+ [TestCase("rgba(0, 0, 0, 0)")]
+ public void ParseFollowedByToRgbaStringShouldRoundtripTheColor(string colorString)
+ {
+ Assert.That(() => Color.Parse(colorString).ToRgbaString(), Is.EqualTo(colorString));
+ }
+
+ [TestCase("rgba(30, 128, 55, 0.5)")]
+ [TestCase("rgba(30, 128, 55, 1)")]
+ [TestCase("rgba(0, 0, 0, 0)")]
+ public void ParseFollowedByToStringShouldRoundtripTheColor(string colorString)
+ {
+ Assert.That(() => Color.Parse(colorString).ToString(), Is.EqualTo(colorString));
+ }
+ [TestCase("#E055D8")]
+ public void ParseFollowedByToHexStringShouldRoundtripTheColor(string colorString)
+ {
+ Assert.That(() => Color.Parse(colorString).ToHexString(), Is.EqualTo(colorString));
+ }
+
+ [TestCase("rgb(1, 2, 3)", 1, 2, 3, 1)]
+ [TestCase("rgb(10%, 20%, 30%)", 25, 51, 76, 1)]
+ [TestCase("rgb(\t1, 2, 3)", 1, 2, 3, 1)]
+ [TestCase("rgba(1, 2, 3, 0.5)", 1, 2, 3, 0.5D)]
+ [TestCase("rgba(10%, 20%, 30%, 0.5)", 25, 51, 76, 0.5D)]
+ [TestCase("#ff00a0", 255, 0, 160, 1)]
+ [TestCase("#01Ff03", 1, 255, 3, 1)]
+ [TestCase("#00FF33", 0, 255, 51, 1)]
+ [TestCase("#0f3", 0, 255, 51, 1)]
+ [TestCase("hsl(120, 100%, 25%)", 0, 128, 0, 1)]
+ [TestCase("hsl(100, 0%, 50%)", 128, 128, 128, 1)]
+ [TestCase("hsl(0, 100%, 50%)", 255, 0, 0, 1)]
+ [TestCase("hsl(120, 100%, 50%)", 0, 255, 0, 1)]
+ [TestCase("hsl(240, 100%, 50%)", 0, 0, 255, 1)]
+ [TestCase("hsl(0, 0%, 100%)", 255, 255, 255, 1)]
+ [TestCase("hsla(120, 100%, 25%, 1)", 0, 128, 0, 1)]
+ [TestCase("hsla(100, 0%, 50%, 0.5)", 128, 128, 128, 0.5)]
+ [TestCase("green", 0, 128, 0, 1)]
+ [TestCase("gray", 128, 128, 128, 1)]
+ [TestCase("transparent", 0, 0, 0, 0)]
+ public void GetHashCodeShouldReturnTheSameResultForEquivalentColors(string colorString, byte red, byte green, byte blue, double alpha)
+ {
+ Assert.That(() => Color.Parse(colorString).GetHashCode(), Is.EqualTo(new Color(red, green, blue, alpha).GetHashCode()));
+ }
+}
\ No newline at end of file
diff --git a/Tests/CSF.Screenplay.Selenium.Tests/ColorsTests.cs b/Tests/CSF.Screenplay.Selenium.Tests/ColorsTests.cs
new file mode 100644
index 00000000..81317277
--- /dev/null
+++ b/Tests/CSF.Screenplay.Selenium.Tests/ColorsTests.cs
@@ -0,0 +1,25 @@
+namespace CSF.Screenplay.Selenium;
+
+[TestFixture, Parallelizable]
+public class ColorsTests
+{
+ [TestCase("GREEN", 0, 128, 0)]
+ [TestCase("green", 0, 128, 0)]
+ [TestCase("gReEn", 0, 128, 0)]
+ public void GetNamedColorShouldReturnAColorIfTheNameIsValid(string colorName, byte r, byte g, byte b)
+ {
+ Assert.That(() => Colors.GetNamedColor(colorName), Is.EqualTo(new Color(r, g, b)));
+ }
+
+ [Test]
+ public void GetNamedColorShouldThrowAneIfTheNameIsNull()
+ {
+ Assert.That(() => Colors.GetNamedColor(null), Throws.ArgumentNullException);
+ }
+
+ [Test]
+ public void GetNamedColorShouldThrowArgExIfTheNameIsInvalid()
+ {
+ Assert.That(() => Colors.GetNamedColor("ElephantsAreNotAColor"), Throws.ArgumentException);
+ }
+}
\ No newline at end of file
diff --git a/Tests/CSF.Screenplay.Selenium.Tests/Queries/QueriesTests.cs b/Tests/CSF.Screenplay.Selenium.Tests/Queries/QueriesTests.cs
index c492a792..61205514 100644
--- a/Tests/CSF.Screenplay.Selenium.Tests/Queries/QueriesTests.cs
+++ b/Tests/CSF.Screenplay.Selenium.Tests/Queries/QueriesTests.cs
@@ -49,7 +49,7 @@ public async Task ReadingTheCssPropertyFromAnElementShouldReturnTheCorrectResult
await Given(webster).WasAbleTo(OpenTheUrl(testPage));
var result = await When(webster).AttemptsTo(ReadFromTheElement(backgroundDiv).TheCssProperty("background-color"));
- Assert.That(result, Is.EqualTo("rgba(170, 170, 255, 1)"));
+ Assert.That(result, Is.EqualTo(Selenium.Color.Parse("rgba(170, 170, 255, 1)")));
}
[Test, Screenplay]