Skip to content

Commit

Permalink
Added escaping of values when using IdStartsWith and IdEndsWith ByPro…
Browse files Browse the repository at this point in the history
…ducers.

This is necessary because we are using a CSS Selector and there are issues with special characters which leads to strange behavior.
  • Loading branch information
slu-it authored and Axel Schüssler committed Oct 13, 2016
1 parent fcc9c23 commit a47f7b1
Show file tree
Hide file tree
Showing 10 changed files with 121 additions and 12 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package info.novatec.testit.webtester.pagefragments.identification;

import java.util.Arrays;
import java.util.Set;
import java.util.stream.Collectors;


/**
* This class includes utility methods for working with CSS Selectors.
*
* @since 2.0.4
*/
public final class CssSelectorUtils {

/** These are all special characters in CSS who need escaping. */
private static final Character[] SPECIAL_CHARS =
{ '!', '"', '#', '$', '%', '&', '\'', '(', ')', '*', '+', ',', '-', '.', '/', ':', ';', '<', '=', '>', '?', '@', '[',
'\\', ']', '^', '`', '{', '|', '}', '~' };
/** See {@link #SPECIAL_CHARS}. */
private static final Set<Character> SPECIAL_CHARS_SET = Arrays.stream(SPECIAL_CHARS).collect(Collectors.toSet());

/**
* Escape the given value by prefixing all {@link #SPECIAL_CHARS} with {@code \}.
* <p>
* This is necessary for all values used by CSS Selectors. For example when matching on the value of an attribute.
*
* @since 2.0.4
*/
public static String escape(String value) {
StringBuilder escapedValue = new StringBuilder(value.length() * 2);
for (Character c : value.toCharArray()) {
if (SPECIAL_CHARS_SET.contains(c)) {
escapedValue.append('\\');
}
escapedValue.append(c);
}
return escapedValue.toString();
}

private CssSelectorUtils() {
// utility class
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@

/**
* This {@link ByProducer} produces a {@link By} using {@link By#cssSelector(String)}.
* <p>
* <b>Important:</b> Don't forget to escape special characters when using this selector!
*
* @see ByProducer
* @since 2.0
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,15 @@
import org.openqa.selenium.By;

import info.novatec.testit.webtester.pagefragments.identification.ByProducer;
import info.novatec.testit.webtester.pagefragments.identification.CssSelectorUtils;


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

@Override
public By createBy(String value) {
return By.cssSelector(String.format(ID_ENDS_WITH_PATTERN, value));
String escapedValue = CssSelectorUtils.escape(value);
return By.cssSelector(String.format(ID_ENDS_WITH_PATTERN, escapedValue));
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,15 @@
import org.openqa.selenium.By;

import info.novatec.testit.webtester.pagefragments.identification.ByProducer;
import info.novatec.testit.webtester.pagefragments.identification.CssSelectorUtils;


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

@Override
public By createBy(String value) {
return By.cssSelector(String.format(ID_STARTS_WITH_PATTERN, value));
String escapedValue = CssSelectorUtils.escape(value);
return By.cssSelector(String.format(ID_STARTS_WITH_PATTERN, escapedValue));
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ public static class IdEndsWithTest {
public void producesCorrectBy() {
By by = ByProducers.idEndsWith(":partial-id");
assertThat(by).isInstanceOf(By.ByCssSelector.class);
assertThat(by).hasToString("By.cssSelector: [id$=':partial-id']");
assertThat(by).hasToString("By.cssSelector: [id$='\\:partial\\-id']");
}

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

}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package info.novatec.testit.webtester.pagefragments.identification;

import static org.assertj.core.api.Assertions.assertThat;

import org.assertj.core.api.SoftAssertions;
import org.junit.Test;


public class CssSelectorUtilsTest {

@Test
public void realisticExample() {
assertThat(CssSelectorUtils.escape("table:form:selection[0]")).isEqualTo("table\\:form\\:selection\\[0\\]");
}

@Test
public void allRelevantSpecialCharactersAreEscaped() {
SoftAssertions softly = new SoftAssertions();
softly.assertThat(CssSelectorUtils.escape("!")).isEqualTo("\\!");
softly.assertThat(CssSelectorUtils.escape("\"")).isEqualTo("\\\"");
softly.assertThat(CssSelectorUtils.escape("#")).isEqualTo("\\#");
softly.assertThat(CssSelectorUtils.escape("$")).isEqualTo("\\$");
softly.assertThat(CssSelectorUtils.escape("%")).isEqualTo("\\%");
softly.assertThat(CssSelectorUtils.escape("&")).isEqualTo("\\&");
softly.assertThat(CssSelectorUtils.escape("'")).isEqualTo("\\'");
softly.assertThat(CssSelectorUtils.escape("(")).isEqualTo("\\(");
softly.assertThat(CssSelectorUtils.escape(")")).isEqualTo("\\)");
softly.assertThat(CssSelectorUtils.escape("*")).isEqualTo("\\*");
softly.assertThat(CssSelectorUtils.escape("+")).isEqualTo("\\+");
softly.assertThat(CssSelectorUtils.escape(",")).isEqualTo("\\,");
softly.assertThat(CssSelectorUtils.escape("-")).isEqualTo("\\-");
softly.assertThat(CssSelectorUtils.escape(".")).isEqualTo("\\.");
softly.assertThat(CssSelectorUtils.escape("/")).isEqualTo("\\/");
softly.assertThat(CssSelectorUtils.escape(":")).isEqualTo("\\:");
softly.assertThat(CssSelectorUtils.escape(";")).isEqualTo("\\;");
softly.assertThat(CssSelectorUtils.escape("<")).isEqualTo("\\<");
softly.assertThat(CssSelectorUtils.escape("=")).isEqualTo("\\=");
softly.assertThat(CssSelectorUtils.escape(">")).isEqualTo("\\>");
softly.assertThat(CssSelectorUtils.escape("?")).isEqualTo("\\?");
softly.assertThat(CssSelectorUtils.escape("@")).isEqualTo("\\@");
softly.assertThat(CssSelectorUtils.escape("[")).isEqualTo("\\[");
softly.assertThat(CssSelectorUtils.escape("\\")).isEqualTo("\\\\");
softly.assertThat(CssSelectorUtils.escape("]")).isEqualTo("\\]");
softly.assertThat(CssSelectorUtils.escape("^")).isEqualTo("\\^");
softly.assertThat(CssSelectorUtils.escape("`")).isEqualTo("\\`");
softly.assertThat(CssSelectorUtils.escape("{")).isEqualTo("\\{");
softly.assertThat(CssSelectorUtils.escape("|")).isEqualTo("\\|");
softly.assertThat(CssSelectorUtils.escape("}")).isEqualTo("\\}");
softly.assertThat(CssSelectorUtils.escape("~")).isEqualTo("\\~");
softly.assertAll();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ public class IdEndsWithTest {
public void producesCorrectBy() {
By by = cut.createBy(":partial-id");
assertThat(by).isInstanceOf(By.ByCssSelector.class);
assertThat(by).hasToString("By.cssSelector: [id$=':partial-id']");
assertThat(by).hasToString("By.cssSelector: [id$='\\:partial\\-id']");
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ public class IdStartsWithTest {
public void producesCorrectBy() {
By by = cut.createBy("partial-id:");
assertThat(by).isInstanceOf(By.ByCssSelector.class);
assertThat(by).hasToString("By.cssSelector: [id^='partial-id:']");
assertThat(by).hasToString("By.cssSelector: [id^='partial\\-id\\:']");
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,9 +90,9 @@ public interface TestPage extends Page {

@IdentifyUsing(value = "id", how = Id.class)
TextField byId();
@IdentifyUsing(value = "prefix-", how = IdStartsWith.class)
@IdentifyUsing(value = "prefix:", how = IdStartsWith.class)
TextField byIdStartsWith();
@IdentifyUsing(value = "-suffix", how = IdEndsWith.class)
@IdentifyUsing(value = ":suffix", how = IdEndsWith.class)
TextField byIdEndsWith();
@IdentifyUsing(value = "//div[@id='xpath']/input", how = XPath.class)
TextField byXpath();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,11 @@ <h3>This page contains elements for testing the ByProducer implementation of Web
</tr>
<tr>
<td>ID_STARTS_WITH</td>
<td><input id="prefix-bar" value="id starts with"></td>
<td><input id="prefix:bar" value="id starts with"></td>
</tr>
<tr>
<td>ID_ENDS_WITH</td>
<td><input id="foo-suffix" value="id ends with"></td>
<td><input id="foo:suffix" value="id ends with"></td>
</tr>
<tr>
<td>XPATH</td>
Expand Down

0 comments on commit a47f7b1

Please sign in to comment.