Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 68 additions & 11 deletions src/FlaUI.WebDriver.UnitTests/Services/ConditionParserTests.cs
Original file line number Diff line number Diff line change
@@ -1,22 +1,79 @@
using FlaUI.WebDriver.Services;
using FlaUI.Core.Conditions;
using FlaUI.UIA3;
using FlaUI.WebDriver.Services;
using NUnit.Framework;

namespace FlaUI.WebDriver.UnitTests.Services
{
public class ConditionParserTests
{
[TestCase("[name=\"2\"]")]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why were the ParseCondition_ByCssAttributeName_ReturnsCondition test cases removed?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The old test was removed because it was testing the same functionality now covered by ParseCondition_CssSelectorWithEscapedSpecialChars_ReturnsNameCondition. Should I keep both for coverage?

[TestCase("*[name=\"2\"]")]
[TestCase("*[name = \"2\"]")]
public void ParseCondition_ByCssAttributeName_ReturnsCondition(string selector)
private ConditionParser _conditionParser;
private ConditionFactory _conditionFactory;

[SetUp]
public void Setup()
{
_conditionParser = new ConditionParser();
var automation = new UIA3Automation();
_conditionFactory = automation.ConditionFactory;
}

[Test]
public void ParseCondition_CssSelectorWithNumericIdUsingUnicodeEscape_ReturnsAutomationIdCondition()
{
var cssSelector = @"#\34 b090d48-e3a5-4eb4-bd37-4bd62dfa6e5b";

var condition = _conditionParser.ParseCondition(_conditionFactory, "css selector", cssSelector);

Assert.That(condition.Value, Is.EqualTo("4b090d48-e3a5-4eb4-bd37-4bd62dfa6e5b"));
}

[Test]
public void ParseCondition_CssSelectorWithSimpleNumericId_ReturnsAutomationIdCondition()
{
var cssSelector = @"#\31 ";
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't "#\31" without the trailing space a better example for this case?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The trailing space is needed when the next character is a hex digit. Without it #\31b would be parsed as a 3-character escape (\31b) instead of \31 followed by b.


var condition = _conditionParser.ParseCondition(_conditionFactory, "css selector", cssSelector);

Assert.That(condition.Value, Is.EqualTo("1"));
}

[Test]
public void ParseCondition_CssSelectorWithEscapedSpecialChars_ReturnsNameCondition()
{
var parser = new ConditionParser();
var uia3 = new UIA3.UIA3Automation();
var cssSelector = @"*[name=""ListBox\ Item\ \#1""]";

var result = parser.ParseCondition(uia3.ConditionFactory, "css selector", selector);
var condition = _conditionParser.ParseCondition(_conditionFactory, "css selector", cssSelector);

Assert.That(condition.Value, Is.EqualTo("ListBox Item #1"));
}

[Test]
public void ParseCondition_CssSelectorCompoundSelector_ThrowsUnsupportedOperation()
{
var cssSelector = "#foo.bar";

Assert.Throws<WebDriverResponseException>(() =>
_conditionParser.ParseCondition(_conditionFactory, "css selector", cssSelector));
}

[Test]
public void ParseCondition_PlainIdStrategy_ReturnsAutomationIdCondition()
{
var id = "TextBox";

var condition = _conditionParser.ParseCondition(_conditionFactory, "id", id);

Assert.That(condition.Value, Is.EqualTo("TextBox"));
}

[Test]
public void ParseCondition_CssSelectorCompoundAttributeSelector_ThrowsUnsupportedOperation()
{
var cssSelector = "[name=\"test\"][class=\"test2\"]";

Assert.That(result.Property, Is.EqualTo(uia3.PropertyLibrary.Element.Name));
Assert.That(result.Value, Is.EqualTo("2"));
Assert.Throws<WebDriverResponseException>(() =>
_conditionParser.ParseCondition(_conditionFactory, "css selector", cssSelector));
}
}
}
}
31 changes: 21 additions & 10 deletions src/FlaUI.WebDriver/Services/ConditionParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,41 +9,44 @@ public class ConditionParser : IConditionParser
/// <summary>
/// Based on https://www.w3.org/TR/CSS21/grammar.html (see also https://www.w3.org/TR/CSS22/grammar.html)
/// Limitations:
/// - Unicode escape characters are not supported.
/// - Multiple selectors are not supported.
/// </summary>
private static Regex SimpleCssIdSelectorRegex = new Regex(@"^#(?<name>(?<nmchar>[_a-z0-9-]|[\240-\377]|(?<escape>\\[^\r\n\f0-9a-f]))+)$", RegexOptions.Compiled | RegexOptions.IgnoreCase);
private static Regex SimpleCssIdSelectorRegex = new Regex(@"^#(?<name>(?<nmchar>[_a-z0-9-]|[\240-\377]|(?<escape>\\[^\r\n\f0-9a-f])|(?<unicode>\\[0-9a-fA-F]{1,6}\s?))+)$", RegexOptions.Compiled | RegexOptions.IgnoreCase);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't the \s? part be omitted here?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The \s? is needed to make the trailing space optional per CSS spec. Unicode escapes like \34 can be followed by an optional space (\34 b or \34b are both valid depending on context).


/// <summary>
/// Based on https://www.w3.org/TR/CSS21/grammar.html (see also https://www.w3.org/TR/CSS22/grammar.html)
/// Limitations:
/// - Unicode escape characters are not supported.
/// - Multiple selectors are not supported.
/// </summary>
private static Regex SimpleCssClassSelectorRegex = new Regex(@"^\.(?<ident>-?(?<nmstart>[_a-z]|[\240-\377])(?<nmchar>[_a-z0-9-]|[\240-\377]|(?<escape>\\[^\r\n\f0-9a-f]))*)$", RegexOptions.Compiled | RegexOptions.IgnoreCase);
private static Regex SimpleCssClassSelectorRegex = new Regex(@"^\.(?<ident>-?(?<nmstart>[_a-z]|[\240-\377])(?<nmchar>[_a-z0-9-]|[\240-\377]|(?<escape>\\[^\r\n\f0-9a-f])|(?<unicode>\\[0-9a-fA-F]{1,6}\s?))*)$", RegexOptions.Compiled | RegexOptions.IgnoreCase);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't the \s? part be omitted here?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This goes the same as the one above


/// <summary>
/// Based on https://www.w3.org/TR/CSS21/grammar.html (see also https://www.w3.org/TR/CSS22/grammar.html)
/// Limitations:
/// - Unicode escape characters or escape characters in the attribute name are not supported.
/// - Escape characters in the attribute name are not supported.
/// - Multiple selectors are not supported.
/// - Attribute presence selector (e.g. `[name]`) not supported.
/// - Attribute equals attribute (e.g. `[name=value]`) not supported.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also update the comment above Unicode escape characters or escape characters in the attribute name are not supported. -> Escape characters in the attribute name are not supported.

/// - ~= or |= not supported.
/// </summary>
private static Regex SimpleCssAttributeSelectorRegex = new Regex(@"^\*?\[\s*(?<ident>-?(?<nmstart>[_a-z]|[\240-\377])(?<nmchar>[_a-z0-9-]|[\240-\377])*)\s*=\s*(?<string>(?<string1>""(?<string1value>([^\n\r\f\\""]|(?<escape>\\[^\r\n\f0-9a-f]))*)"")|(?<string2>'(?<string2value>([^\n\r\f\\']|(?<escape>\\[^\r\n\f0-9a-f]))*)'))\s*\]$", RegexOptions.Compiled | RegexOptions.IgnoreCase);
private static Regex SimpleCssAttributeSelectorRegex = new Regex(@"^\*?\[\s*(?<ident>-?(?<nmstart>[_a-z]|[\240-\377])(?<nmchar>[_a-z0-9-]|[\240-\377])*)\s*=\s*(?<string>(?<string1>""(?<string1value>([^\n\r\f\\""]|(?<escape>\\[^\r\n\f0-9a-f])|(?<unicode>\\[0-9a-fA-F]{1,6}\s?))*)"")|(?<string2>'(?<string2value>([^\n\r\f\\']|(?<escape>\\[^\r\n\f0-9a-f])|(?<unicode>\\[0-9a-fA-F]{1,6}\s?))*)'))\s*\]$", RegexOptions.Compiled | RegexOptions.IgnoreCase);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't the \s? part be omitted here?


/// <summary>
/// Based on https://www.w3.org/TR/CSS21/grammar.html (see also https://www.w3.org/TR/CSS22/grammar.html)
/// Limitations:
/// - Unicode escape characters are not supported.
/// Matches simple escape characters (e.g., \#)
/// </summary>
private static Regex SimpleCssEscapeCharacterRegex = new Regex(@"\\[^\r\n\f0-9a-f]", RegexOptions.Compiled | RegexOptions.IgnoreCase);

/// <summary>
/// Matches CSS unicode escape sequences (e.g., \34 or \000034 followed by optional space)
/// </summary>
private static Regex CssUnicodeEscapeRegex = new Regex(@"\\([0-9a-fA-F]{1,6})\s?", RegexOptions.Compiled);

public PropertyCondition ParseCondition(ConditionFactory conditionFactory, string @using, string value)
{
switch (@using)
{
case "id":
case "accessibility id":
return conditionFactory.ByAutomationId(value);
case "name":
Expand Down Expand Up @@ -86,8 +89,16 @@ public PropertyCondition ParseCondition(ConditionFactory conditionFactory, strin

private static string ReplaceCssEscapedCharacters(string value)
{
return SimpleCssEscapeCharacterRegex.Replace(value, match => match.Value.Substring(1));
}
var result = CssUnicodeEscapeRegex.Replace(value, m =>
{
var hexValue = m.Groups[1].Value;
var decodedChar = ((char)Convert.ToInt32(hexValue, 16)).ToString();
return decodedChar;
});

result = SimpleCssEscapeCharacterRegex.Replace(result, match => match.Value.Substring(1));

return result;
}
}
}