-
Notifications
You must be signed in to change notification settings - Fork 13
Fix CSS selector support for numeric IDs and escape sequences #150
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
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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\"]")] | ||
| [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 "; | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Isn't
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
||
|
|
||
| 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)); | ||
| } | ||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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); | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Shouldn't the
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The |
||
|
|
||
| /// <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); | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Shouldn't the
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also update the comment above |
||
| /// - ~= 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); | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Shouldn't the |
||
|
|
||
| /// <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": | ||
|
|
@@ -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; | ||
| } | ||
| } | ||
| } | ||
There was a problem hiding this comment.
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_ReturnsConditiontest cases removed?There was a problem hiding this comment.
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?