Skip to content
This repository was archived by the owner on Mar 25, 2025. It is now read-only.

Commit a47f7b1

Browse files
slu-itAxel Schüssler
authored andcommitted
Added escaping of values when using IdStartsWith and IdEndsWith ByProducers.
This is necessary because we are using a CSS Selector and there are issues with special characters which leads to strange behavior.
1 parent fcc9c23 commit a47f7b1

File tree

10 files changed

+121
-12
lines changed

10 files changed

+121
-12
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package info.novatec.testit.webtester.pagefragments.identification;
2+
3+
import java.util.Arrays;
4+
import java.util.Set;
5+
import java.util.stream.Collectors;
6+
7+
8+
/**
9+
* This class includes utility methods for working with CSS Selectors.
10+
*
11+
* @since 2.0.4
12+
*/
13+
public final class CssSelectorUtils {
14+
15+
/** These are all special characters in CSS who need escaping. */
16+
private static final Character[] SPECIAL_CHARS =
17+
{ '!', '"', '#', '$', '%', '&', '\'', '(', ')', '*', '+', ',', '-', '.', '/', ':', ';', '<', '=', '>', '?', '@', '[',
18+
'\\', ']', '^', '`', '{', '|', '}', '~' };
19+
/** See {@link #SPECIAL_CHARS}. */
20+
private static final Set<Character> SPECIAL_CHARS_SET = Arrays.stream(SPECIAL_CHARS).collect(Collectors.toSet());
21+
22+
/**
23+
* Escape the given value by prefixing all {@link #SPECIAL_CHARS} with {@code \}.
24+
* <p>
25+
* This is necessary for all values used by CSS Selectors. For example when matching on the value of an attribute.
26+
*
27+
* @since 2.0.4
28+
*/
29+
public static String escape(String value) {
30+
StringBuilder escapedValue = new StringBuilder(value.length() * 2);
31+
for (Character c : value.toCharArray()) {
32+
if (SPECIAL_CHARS_SET.contains(c)) {
33+
escapedValue.append('\\');
34+
}
35+
escapedValue.append(c);
36+
}
37+
return escapedValue.toString();
38+
}
39+
40+
private CssSelectorUtils() {
41+
// utility class
42+
}
43+
44+
}

webtester-core/src/main/java/info/novatec/testit/webtester/pagefragments/identification/producers/CssSelector.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77

88
/**
99
* This {@link ByProducer} produces a {@link By} using {@link By#cssSelector(String)}.
10+
* <p>
11+
* <b>Important:</b> Don't forget to escape special characters when using this selector!
1012
*
1113
* @see ByProducer
1214
* @since 2.0

webtester-core/src/main/java/info/novatec/testit/webtester/pagefragments/identification/producers/IdEndsWith.java

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,15 @@
33
import org.openqa.selenium.By;
44

55
import info.novatec.testit.webtester.pagefragments.identification.ByProducer;
6+
import info.novatec.testit.webtester.pagefragments.identification.CssSelectorUtils;
67

78

89
/**
910
* This {@link ByProducer} produces a {@link By} using {@link By#cssSelector(String)} to partially match an ID
10-
* (ends-with).
11+
* (ends-with). The given value will be escaped using {@link CssSelectorUtils#escape(String)} in order to prevent special
12+
* characters from interfering with the selection.
13+
* <p>
14+
* <b>Example:</b> {@code :form:table:selection[5]} will be escaped to {@code \:form\:table\:selection\[5\]}
1115
*
1216
* @see ByProducer
1317
* @since 2.0
@@ -18,7 +22,8 @@ public class IdEndsWith implements ByProducer {
1822

1923
@Override
2024
public By createBy(String value) {
21-
return By.cssSelector(String.format(ID_ENDS_WITH_PATTERN, value));
25+
String escapedValue = CssSelectorUtils.escape(value);
26+
return By.cssSelector(String.format(ID_ENDS_WITH_PATTERN, escapedValue));
2227
}
2328

2429
@Override

webtester-core/src/main/java/info/novatec/testit/webtester/pagefragments/identification/producers/IdStartsWith.java

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,15 @@
33
import org.openqa.selenium.By;
44

55
import info.novatec.testit.webtester.pagefragments.identification.ByProducer;
6+
import info.novatec.testit.webtester.pagefragments.identification.CssSelectorUtils;
67

78

89
/**
910
* This {@link ByProducer} produces a {@link By} using {@link By#cssSelector(String)} to partially match an ID
10-
* (starts-with).
11+
* (starts-with). The given value will be escaped using {@link CssSelectorUtils#escape(String)} in order to prevent special
12+
* characters from interfering with the selection.
13+
* <p>
14+
* <b>Example:</b> {@code form:table:selection[5]} will be escaped to {@code form\:table\:selection\[5\]}
1115
*
1216
* @see ByProducer
1317
* @since 2.0
@@ -18,7 +22,8 @@ public class IdStartsWith implements ByProducer {
1822

1923
@Override
2024
public By createBy(String value) {
21-
return By.cssSelector(String.format(ID_STARTS_WITH_PATTERN, value));
25+
String escapedValue = CssSelectorUtils.escape(value);
26+
return By.cssSelector(String.format(ID_STARTS_WITH_PATTERN, escapedValue));
2227
}
2328

2429
@Override

webtester-core/src/test/java/info/novatec/testit/webtester/pagefragments/identification/ByProducersTest.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ public static class IdEndsWithTest {
4545
public void producesCorrectBy() {
4646
By by = ByProducers.idEndsWith(":partial-id");
4747
assertThat(by).isInstanceOf(By.ByCssSelector.class);
48-
assertThat(by).hasToString("By.cssSelector: [id$=':partial-id']");
48+
assertThat(by).hasToString("By.cssSelector: [id$='\\:partial\\-id']");
4949
}
5050

5151
}
@@ -56,7 +56,7 @@ public static class IdStartsWithTest {
5656
public void producesCorrectBy() {
5757
By by = ByProducers.idStartsWith("partial-id:");
5858
assertThat(by).isInstanceOf(By.ByCssSelector.class);
59-
assertThat(by).hasToString("By.cssSelector: [id^='partial-id:']");
59+
assertThat(by).hasToString("By.cssSelector: [id^='partial\\-id\\:']");
6060
}
6161

6262
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
package info.novatec.testit.webtester.pagefragments.identification;
2+
3+
import static org.assertj.core.api.Assertions.assertThat;
4+
5+
import org.assertj.core.api.SoftAssertions;
6+
import org.junit.Test;
7+
8+
9+
public class CssSelectorUtilsTest {
10+
11+
@Test
12+
public void realisticExample() {
13+
assertThat(CssSelectorUtils.escape("table:form:selection[0]")).isEqualTo("table\\:form\\:selection\\[0\\]");
14+
}
15+
16+
@Test
17+
public void allRelevantSpecialCharactersAreEscaped() {
18+
SoftAssertions softly = new SoftAssertions();
19+
softly.assertThat(CssSelectorUtils.escape("!")).isEqualTo("\\!");
20+
softly.assertThat(CssSelectorUtils.escape("\"")).isEqualTo("\\\"");
21+
softly.assertThat(CssSelectorUtils.escape("#")).isEqualTo("\\#");
22+
softly.assertThat(CssSelectorUtils.escape("$")).isEqualTo("\\$");
23+
softly.assertThat(CssSelectorUtils.escape("%")).isEqualTo("\\%");
24+
softly.assertThat(CssSelectorUtils.escape("&")).isEqualTo("\\&");
25+
softly.assertThat(CssSelectorUtils.escape("'")).isEqualTo("\\'");
26+
softly.assertThat(CssSelectorUtils.escape("(")).isEqualTo("\\(");
27+
softly.assertThat(CssSelectorUtils.escape(")")).isEqualTo("\\)");
28+
softly.assertThat(CssSelectorUtils.escape("*")).isEqualTo("\\*");
29+
softly.assertThat(CssSelectorUtils.escape("+")).isEqualTo("\\+");
30+
softly.assertThat(CssSelectorUtils.escape(",")).isEqualTo("\\,");
31+
softly.assertThat(CssSelectorUtils.escape("-")).isEqualTo("\\-");
32+
softly.assertThat(CssSelectorUtils.escape(".")).isEqualTo("\\.");
33+
softly.assertThat(CssSelectorUtils.escape("/")).isEqualTo("\\/");
34+
softly.assertThat(CssSelectorUtils.escape(":")).isEqualTo("\\:");
35+
softly.assertThat(CssSelectorUtils.escape(";")).isEqualTo("\\;");
36+
softly.assertThat(CssSelectorUtils.escape("<")).isEqualTo("\\<");
37+
softly.assertThat(CssSelectorUtils.escape("=")).isEqualTo("\\=");
38+
softly.assertThat(CssSelectorUtils.escape(">")).isEqualTo("\\>");
39+
softly.assertThat(CssSelectorUtils.escape("?")).isEqualTo("\\?");
40+
softly.assertThat(CssSelectorUtils.escape("@")).isEqualTo("\\@");
41+
softly.assertThat(CssSelectorUtils.escape("[")).isEqualTo("\\[");
42+
softly.assertThat(CssSelectorUtils.escape("\\")).isEqualTo("\\\\");
43+
softly.assertThat(CssSelectorUtils.escape("]")).isEqualTo("\\]");
44+
softly.assertThat(CssSelectorUtils.escape("^")).isEqualTo("\\^");
45+
softly.assertThat(CssSelectorUtils.escape("`")).isEqualTo("\\`");
46+
softly.assertThat(CssSelectorUtils.escape("{")).isEqualTo("\\{");
47+
softly.assertThat(CssSelectorUtils.escape("|")).isEqualTo("\\|");
48+
softly.assertThat(CssSelectorUtils.escape("}")).isEqualTo("\\}");
49+
softly.assertThat(CssSelectorUtils.escape("~")).isEqualTo("\\~");
50+
softly.assertAll();
51+
}
52+
53+
}

webtester-core/src/test/java/info/novatec/testit/webtester/pagefragments/identification/producers/IdEndsWithTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ public class IdEndsWithTest {
1414
public void producesCorrectBy() {
1515
By by = cut.createBy(":partial-id");
1616
assertThat(by).isInstanceOf(By.ByCssSelector.class);
17-
assertThat(by).hasToString("By.cssSelector: [id$=':partial-id']");
17+
assertThat(by).hasToString("By.cssSelector: [id$='\\:partial\\-id']");
1818
}
1919

2020
@Test

webtester-core/src/test/java/info/novatec/testit/webtester/pagefragments/identification/producers/IdStartsWithTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ public class IdStartsWithTest {
1414
public void producesCorrectBy() {
1515
By by = cut.createBy("partial-id:");
1616
assertThat(by).isInstanceOf(By.ByCssSelector.class);
17-
assertThat(by).hasToString("By.cssSelector: [id^='partial-id:']");
17+
assertThat(by).hasToString("By.cssSelector: [id^='partial\\-id\\:']");
1818
}
1919

2020
@Test

webtester-core/src/test/java/integration/pagefragments/identification/IdentificationIntegrationTest.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,9 +90,9 @@ public interface TestPage extends Page {
9090

9191
@IdentifyUsing(value = "id", how = Id.class)
9292
TextField byId();
93-
@IdentifyUsing(value = "prefix-", how = IdStartsWith.class)
93+
@IdentifyUsing(value = "prefix:", how = IdStartsWith.class)
9494
TextField byIdStartsWith();
95-
@IdentifyUsing(value = "-suffix", how = IdEndsWith.class)
95+
@IdentifyUsing(value = ":suffix", how = IdEndsWith.class)
9696
TextField byIdEndsWith();
9797
@IdentifyUsing(value = "//div[@id='xpath']/input", how = XPath.class)
9898
TextField byXpath();

webtester-core/src/test/resources/html/pagefragments/identification/by-producers.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,11 @@ <h3>This page contains elements for testing the ByProducer implementation of Web
1919
</tr>
2020
<tr>
2121
<td>ID_STARTS_WITH</td>
22-
<td><input id="prefix-bar" value="id starts with"></td>
22+
<td><input id="prefix:bar" value="id starts with"></td>
2323
</tr>
2424
<tr>
2525
<td>ID_ENDS_WITH</td>
26-
<td><input id="foo-suffix" value="id ends with"></td>
26+
<td><input id="foo:suffix" value="id ends with"></td>
2727
</tr>
2828
<tr>
2929
<td>XPATH</td>

0 commit comments

Comments
 (0)