Skip to content

Commit 81ad120

Browse files
committed
Fix CSS selector support for numeric IDs and escape sequences
1 parent 3cd26e6 commit 81ad120

File tree

2 files changed

+89
-21
lines changed

2 files changed

+89
-21
lines changed
Lines changed: 68 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,79 @@
1-
using FlaUI.WebDriver.Services;
1+
using FlaUI.Core.Conditions;
2+
using FlaUI.UIA3;
3+
using FlaUI.WebDriver.Services;
24
using NUnit.Framework;
35

46
namespace FlaUI.WebDriver.UnitTests.Services
57
{
68
public class ConditionParserTests
79
{
8-
[TestCase("[name=\"2\"]")]
9-
[TestCase("*[name=\"2\"]")]
10-
[TestCase("*[name = \"2\"]")]
11-
public void ParseCondition_ByCssAttributeName_ReturnsCondition(string selector)
10+
private ConditionParser _conditionParser;
11+
private ConditionFactory _conditionFactory;
12+
13+
[SetUp]
14+
public void Setup()
15+
{
16+
_conditionParser = new ConditionParser();
17+
var automation = new UIA3Automation();
18+
_conditionFactory = automation.ConditionFactory;
19+
}
20+
21+
[Test]
22+
public void ParseCondition_CssSelectorWithNumericIdUsingUnicodeEscape_ReturnsAutomationIdCondition()
23+
{
24+
var cssSelector = @"#\34 b090d48-e3a5-4eb4-bd37-4bd62dfa6e5b";
25+
26+
var condition = _conditionParser.ParseCondition(_conditionFactory, "css selector", cssSelector);
27+
28+
Assert.That(condition.Value, Is.EqualTo("4b090d48-e3a5-4eb4-bd37-4bd62dfa6e5b"));
29+
}
30+
31+
[Test]
32+
public void ParseCondition_CssSelectorWithSimpleNumericId_ReturnsAutomationIdCondition()
33+
{
34+
var cssSelector = @"#\31 ";
35+
36+
var condition = _conditionParser.ParseCondition(_conditionFactory, "css selector", cssSelector);
37+
38+
Assert.That(condition.Value, Is.EqualTo("1"));
39+
}
40+
41+
[Test]
42+
public void ParseCondition_CssSelectorWithEscapedSpecialChars_ReturnsNameCondition()
1243
{
13-
var parser = new ConditionParser();
14-
var uia3 = new UIA3.UIA3Automation();
44+
var cssSelector = @"*[name=""ListBox\ Item\ \#1""]";
1545

16-
var result = parser.ParseCondition(uia3.ConditionFactory, "css selector", selector);
46+
var condition = _conditionParser.ParseCondition(_conditionFactory, "css selector", cssSelector);
47+
48+
Assert.That(condition.Value, Is.EqualTo("ListBox Item #1"));
49+
}
50+
51+
[Test]
52+
public void ParseCondition_CssSelectorCompoundSelector_ThrowsUnsupportedOperation()
53+
{
54+
var cssSelector = "#foo.bar";
55+
56+
Assert.Throws<WebDriverResponseException>(() =>
57+
_conditionParser.ParseCondition(_conditionFactory, "css selector", cssSelector));
58+
}
59+
60+
[Test]
61+
public void ParseCondition_PlainIdStrategy_ReturnsAutomationIdCondition()
62+
{
63+
var id = "TextBox";
64+
65+
var condition = _conditionParser.ParseCondition(_conditionFactory, "id", id);
66+
67+
Assert.That(condition.Value, Is.EqualTo("TextBox"));
68+
}
69+
70+
[Test]
71+
public void ParseCondition_CssSelectorCompoundAttributeSelector_ThrowsUnsupportedOperation()
72+
{
73+
var cssSelector = "[name=\"test\"][class=\"test2\"]";
1774

18-
Assert.That(result.Property, Is.EqualTo(uia3.PropertyLibrary.Element.Name));
19-
Assert.That(result.Value, Is.EqualTo("2"));
75+
Assert.Throws<WebDriverResponseException>(() =>
76+
_conditionParser.ParseCondition(_conditionFactory, "css selector", cssSelector));
2077
}
2178
}
22-
}
79+
}

src/FlaUI.WebDriver/Services/ConditionParser.cs

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,41 +9,44 @@ public class ConditionParser : IConditionParser
99
/// <summary>
1010
/// Based on https://www.w3.org/TR/CSS21/grammar.html (see also https://www.w3.org/TR/CSS22/grammar.html)
1111
/// Limitations:
12-
/// - Unicode escape characters are not supported.
1312
/// - Multiple selectors are not supported.
1413
/// </summary>
15-
private static Regex SimpleCssIdSelectorRegex = new Regex(@"^#(?<name>(?<nmchar>[_a-z0-9-]|[\240-\377]|(?<escape>\\[^\r\n\f0-9a-f]))+)$", RegexOptions.Compiled | RegexOptions.IgnoreCase);
14+
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);
1615

1716
/// <summary>
1817
/// Based on https://www.w3.org/TR/CSS21/grammar.html (see also https://www.w3.org/TR/CSS22/grammar.html)
1918
/// Limitations:
20-
/// - Unicode escape characters are not supported.
2119
/// - Multiple selectors are not supported.
2220
/// </summary>
23-
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);
21+
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);
2422

2523
/// <summary>
2624
/// Based on https://www.w3.org/TR/CSS21/grammar.html (see also https://www.w3.org/TR/CSS22/grammar.html)
2725
/// Limitations:
28-
/// - Unicode escape characters or escape characters in the attribute name are not supported.
26+
/// - Escape characters in the attribute name are not supported.
2927
/// - Multiple selectors are not supported.
3028
/// - Attribute presence selector (e.g. `[name]`) not supported.
3129
/// - Attribute equals attribute (e.g. `[name=value]`) not supported.
3230
/// - ~= or |= not supported.
3331
/// </summary>
34-
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);
32+
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);
3533

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

40+
/// <summary>
41+
/// Matches CSS unicode escape sequences (e.g., \34 or \000034 followed by optional space)
42+
/// </summary>
43+
private static Regex CssUnicodeEscapeRegex = new Regex(@"\\([0-9a-fA-F]{1,6})\s?", RegexOptions.Compiled);
44+
4345
public PropertyCondition ParseCondition(ConditionFactory conditionFactory, string @using, string value)
4446
{
4547
switch (@using)
4648
{
49+
case "id":
4750
case "accessibility id":
4851
return conditionFactory.ByAutomationId(value);
4952
case "name":
@@ -86,8 +89,16 @@ public PropertyCondition ParseCondition(ConditionFactory conditionFactory, strin
8689

8790
private static string ReplaceCssEscapedCharacters(string value)
8891
{
89-
return SimpleCssEscapeCharacterRegex.Replace(value, match => match.Value.Substring(1));
90-
}
92+
var result = CssUnicodeEscapeRegex.Replace(value, m =>
93+
{
94+
var hexValue = m.Groups[1].Value;
95+
var decodedChar = ((char)Convert.ToInt32(hexValue, 16)).ToString();
96+
return decodedChar;
97+
});
9198

99+
result = SimpleCssEscapeCharacterRegex.Replace(result, match => match.Value.Substring(1));
100+
101+
return result;
102+
}
92103
}
93104
}

0 commit comments

Comments
 (0)