Skip to content

Commit 85c5f90

Browse files
authored
chore: update selector escaping, roll driver (#1385)
1 parent 30778a3 commit 85c5f90

File tree

6 files changed

+90
-40
lines changed

6 files changed

+90
-40
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ Playwright is a Java library to automate [Chromium](https://www.chromium.org/Hom
1111

1212
| | Linux | macOS | Windows |
1313
| :--- | :---: | :---: | :---: |
14-
| Chromium <!-- GEN:chromium-version -->117.0.5938.48<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
14+
| Chromium <!-- GEN:chromium-version -->117.0.5938.62<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
1515
| WebKit <!-- GEN:webkit-version -->17.0<!-- GEN:stop --> ||||
1616
| Firefox <!-- GEN:firefox-version -->117.0<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
1717

playwright/src/main/java/com/microsoft/playwright/impl/LocatorUtils.java

Lines changed: 24 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
import java.util.regex.Pattern;
77

8+
import static com.microsoft.playwright.impl.Serialization.gson;
89
import static com.microsoft.playwright.impl.Utils.toJsRegexFlags;
910

1011
public class LocatorUtils {
@@ -25,10 +26,7 @@ static String getByLabelSelector(Object text, Locator.GetByLabelOptions options)
2526
}
2627

2728
private static String getByAttributeTextSelector(String attrName, Object value, boolean exact) {
28-
if (value instanceof Pattern) {
29-
return "internal:attr=[" + attrName + "=" + toJsRegExp((Pattern) value) + "]";
30-
}
31-
return "internal:attr=[" + attrName + "=" + escapeForAttributeSelector((String) value, exact) + "]";
29+
return "internal:attr=[" + attrName + "=" + escapeForAttributeSelector(value, exact) + "]";
3230
}
3331

3432
static String getByTestIdSelector(Object testId) {
@@ -71,14 +69,7 @@ static String getByRoleSelector(AriaRole role, Locator.GetByRoleOptions options)
7169
if (options.level != null)
7270
addAttr(result, "level", options.level.toString());
7371
if (options.name != null) {
74-
String name;
75-
if (options.name instanceof String) {
76-
name = escapeForAttributeSelector((String) options.name, options.exact != null && options.exact);
77-
} else if (options.name instanceof Pattern) {
78-
name = toJsRegExp((Pattern) options.name);
79-
} else {
80-
throw new IllegalArgumentException("options.name can be String or Pattern, found: " + options.name);
81-
}
72+
String name = escapeForAttributeSelector(options.name, options.exact != null && options.exact);
8273
addAttr(result, "name", name);
8374
}
8475
if (options.pressed != null)
@@ -87,38 +78,33 @@ static String getByRoleSelector(AriaRole role, Locator.GetByRoleOptions options)
8778
return result.toString();
8879
}
8980

90-
static String escapeForTextSelector(Object text, boolean exact) {
91-
return escapeForTextSelector(text, exact, false);
81+
private static String escapeRegexForSelector(Pattern re) {
82+
// Even number of backslashes followed by the quote -> insert a backslash.
83+
return toJsRegExp(re).replaceAll("(^|[^\\\\])(\\\\\\\\)*([\"'`])", "$1$2\\\\$3").replaceAll(">>", "\\\\>\\\\>");
9284
}
9385

94-
private static String escapeForTextSelector(Object param, boolean exact, boolean caseSensitive) {
95-
if (param instanceof Pattern) {
96-
return toJsRegExp((Pattern) param);
97-
}
98-
if (!(param instanceof String)) {
99-
throw new IllegalArgumentException("text parameter must be Pattern or String: " + param);
100-
}
101-
String text = (String) param;
102-
if (exact) {
103-
return '"' + text.replace("\"", "\\\"") + '"';
86+
static String escapeForTextSelector(Object value, boolean exact) {
87+
if (value instanceof Pattern) {
88+
return escapeRegexForSelector((Pattern) value);
10489
}
105-
106-
if (text.contains("\"") || text.contains(">>") || text.startsWith("/")) {
107-
return "/" + escapeForRegex(text).replaceAll("\\s+", "\\\\s+") + "/" + (caseSensitive ? "" : "i");
90+
if (value instanceof String) {
91+
return gson().toJson(value) + (exact ? "s" : "i");
10892
}
109-
return text;
93+
throw new IllegalArgumentException("text parameter must be Pattern or String: " + value);
11094
}
11195

112-
private static String escapeForRegex(String text) {
113-
return text.replaceAll("[.*+?^>${}()|\\[\\]\\\\]", "\\\\\\\\$0");
114-
}
115-
116-
private static String escapeForAttributeSelector(String value, boolean exact) {
117-
// TODO: this should actually be
118-
// cssEscape(value).replace(/\\ /g, ' ')
119-
// However, our attribute selectors do not conform to CSS parsing spec,
120-
// so we escape them differently.
121-
return '"' + value.replaceAll("\\\\", "\\\\\\\\").replaceAll("\"", "\\\\\"") + '"' + (exact ? "" : "i");
96+
private static String escapeForAttributeSelector(Object value, boolean exact) {
97+
if (value instanceof Pattern) {
98+
return escapeRegexForSelector((Pattern) value);
99+
}
100+
if (value instanceof String) {
101+
// TODO: this should actually be
102+
// cssEscape(value).replace(/\\ /g, ' ')
103+
// However, our attribute selectors do not conform to CSS parsing spec,
104+
// so we escape them differently.
105+
return '"' + ((String) value).replaceAll("\\\\", "\\\\\\\\").replaceAll("\"", "\\\\\"") + '"' + (exact ? "" : "i");
106+
}
107+
throw new IllegalArgumentException("Attribute can be String or Pattern, found: " + value);
122108
}
123109

124110
private static String toJsRegExp(Pattern pattern) {

playwright/src/test/java/com/microsoft/playwright/TestPageLocatorQuery.java

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,39 @@ void shouldFilterByRegexWithQuotes() {
107107
assertEquals("Hello \"world\"", page.locator("div", new Page.LocatorOptions().setHasText(Pattern.compile("Hello \"world\""))).textContent());
108108
}
109109

110+
@Test
111+
void shouldFilterByRegexWithASingleQuote() {
112+
page.setContent("<button>let's let's<span>hello</span></button>");
113+
assertThat(page.locator("button", new Page.LocatorOptions().setHasText(Pattern.compile("let's", Pattern.CASE_INSENSITIVE))).locator("span")).hasText("hello");
114+
assertThat(page.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setName(Pattern.compile("let's", Pattern.CASE_INSENSITIVE))).locator("span")).hasText("hello");
115+
assertThat(page.locator("button", new Page.LocatorOptions().setHasText(Pattern.compile("let\'s", Pattern.CASE_INSENSITIVE))).locator("span")).hasText("hello");
116+
assertThat(page.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setName(Pattern.compile("let\'s", Pattern.CASE_INSENSITIVE))).locator("span")).hasText("hello");
117+
assertThat(page.locator("button", new Page.LocatorOptions().setHasText(Pattern.compile("'s", Pattern.CASE_INSENSITIVE))).locator("span")).hasText("hello");
118+
assertThat(page.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setName(Pattern.compile("'s", Pattern.CASE_INSENSITIVE))).locator("span")).hasText("hello");
119+
assertThat(page.locator("button", new Page.LocatorOptions().setHasText(Pattern.compile("\'s", Pattern.CASE_INSENSITIVE))).locator("span")).hasText("hello");
120+
assertThat(page.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setName(Pattern.compile("\'s", Pattern.CASE_INSENSITIVE))).locator("span")).hasText("hello");
121+
assertThat(page.locator("button", new Page.LocatorOptions().setHasText(Pattern.compile("let['abc]s", Pattern.CASE_INSENSITIVE))).locator("span")).hasText("hello");
122+
assertThat(page.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setName(Pattern.compile("let['abc]s", Pattern.CASE_INSENSITIVE))).locator("span")).hasText("hello");
123+
assertThat(page.locator("button", new Page.LocatorOptions().setHasText(Pattern.compile("let\\\\'s", Pattern.CASE_INSENSITIVE)))).not().isVisible();
124+
assertThat(page.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setName(Pattern.compile("let\\\\'s", Pattern.CASE_INSENSITIVE)))).not().isVisible();
125+
assertThat(page.locator("button", new Page.LocatorOptions().setHasText(Pattern.compile("let's let\\'s", Pattern.CASE_INSENSITIVE))).locator("span")).hasText("hello");
126+
assertThat(page.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setName(Pattern.compile("let's let\\'s", Pattern.CASE_INSENSITIVE))).locator("span")).hasText("hello");
127+
assertThat(page.locator("button", new Page.LocatorOptions().setHasText(Pattern.compile("let\\'s let's", Pattern.CASE_INSENSITIVE))).locator("span")).hasText("hello");
128+
assertThat(page.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setName(Pattern.compile("let\\'s let's", Pattern.CASE_INSENSITIVE))).locator("span")).hasText("hello");
129+
130+
page.setContent("<button>let\\'s let\\'s<span>hello</span></button>");
131+
assertThat(page.locator("button", new Page.LocatorOptions().setHasText(Pattern.compile("let\\'s", Pattern.CASE_INSENSITIVE)))).not().isVisible();
132+
assertThat(page.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setName(Pattern.compile("let\\'s", Pattern.CASE_INSENSITIVE)))).not().isVisible();
133+
assertThat(page.locator("button", new Page.LocatorOptions().setHasText(Pattern.compile("let\\\\'s", Pattern.CASE_INSENSITIVE))).locator("span")).hasText("hello");
134+
assertThat(page.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setName(Pattern.compile("let\\\\'s", Pattern.CASE_INSENSITIVE))).locator("span")).hasText("hello");
135+
assertThat(page.locator("button", new Page.LocatorOptions().setHasText(Pattern.compile("let\\\\\\'s", Pattern.CASE_INSENSITIVE))).locator("span")).hasText("hello");
136+
assertThat(page.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setName(Pattern.compile("let\\\\\\'s", Pattern.CASE_INSENSITIVE))).locator("span")).hasText("hello");
137+
assertThat(page.locator("button", new Page.LocatorOptions().setHasText(Pattern.compile("let\\\\'s let\\\\\\'s", Pattern.CASE_INSENSITIVE))).locator("span")).hasText("hello");
138+
assertThat(page.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setName(Pattern.compile("let\\\\'s let\\\\\\'s", Pattern.CASE_INSENSITIVE))).locator("span")).hasText("hello");
139+
assertThat(page.locator("button", new Page.LocatorOptions().setHasText(Pattern.compile("let\\\\\\'s let\\\\'s", Pattern.CASE_INSENSITIVE))).locator("span")).hasText("hello");
140+
assertThat(page.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setName(Pattern.compile("let\\\\\\'s let\\\\'s", Pattern.CASE_INSENSITIVE))).locator("span")).hasText("hello");
141+
}
142+
110143
@Test
111144
void shouldFilterByRegexAndRegexpFlags() {
112145
page.setContent("<div>Hello \"world\"</div><div>Hello world</div>");

playwright/src/test/java/com/microsoft/playwright/TestSelectorsGetBy.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,25 @@ void getByEscaping() {
153153
assertThat(page.getByTitle("my title", new Page.GetByTitleOptions().setExact(true))).hasCount(1, new LocatorAssertions.HasCountOptions().setTimeout(500));
154154
assertThat(page.getByTitle("my t\\itle", new Page.GetByTitleOptions().setExact(true))).hasCount(0, new LocatorAssertions.HasCountOptions().setTimeout(500));
155155
assertThat(page.getByTitle("my t\\\\itle", new Page.GetByTitleOptions().setExact(true))).hasCount(0, new LocatorAssertions.HasCountOptions().setTimeout(500));
156+
157+
page.setContent("<label for=target>foo &gt;&gt; bar</label><input id=target>");
158+
page.evalOnSelector("input", "input => {\n" +
159+
" input.setAttribute('placeholder', 'foo >> bar');\n" +
160+
" input.setAttribute('title', 'foo >> bar');\n" +
161+
" input.setAttribute('alt', 'foo >> bar');\n" +
162+
" }");
163+
assertEquals("foo >> bar", page.getByText("foo >> bar").textContent());
164+
assertThat(page.locator("label")).hasText("foo >> bar");
165+
assertThat(page.getByText("foo >> bar")).hasText("foo >> bar");
166+
assertEquals("foo >> bar", page.getByText(Pattern.compile("foo >> bar")).textContent());
167+
assertThat(page.getByLabel("foo >> bar")).hasAttribute("id", "target");
168+
assertThat(page.getByLabel(Pattern.compile("foo >> bar"))).hasAttribute("id", "target");
169+
assertThat(page.getByPlaceholder("foo >> bar")).hasAttribute("id", "target");
170+
assertThat(page.getByAltText("foo >> bar")).hasAttribute("id", "target");
171+
assertThat(page.getByTitle("foo >> bar")).hasAttribute("id", "target");
172+
assertThat(page.getByPlaceholder(Pattern.compile("foo >> bar"))).hasAttribute("id", "target");
173+
assertThat(page.getByAltText(Pattern.compile("foo >> bar"))).hasAttribute("id", "target");
174+
assertThat(page.getByTitle(Pattern.compile("foo >> bar"))).hasAttribute("id", "target");
156175
}
157176

158177
@Test

playwright/src/test/java/com/microsoft/playwright/TestSelectorsText.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,18 @@
99
import static org.junit.jupiter.api.Assertions.assertEquals;
1010

1111
public class TestSelectorsText extends TestBase {
12+
13+
@Test
14+
void shouldWorkSmoke() {
15+
page.setContent("<div>Hi&gt;&gt;<span></span></div>");
16+
assertEquals("<span></span>", page.evalOnSelector("text=\"Hi>>\">>span", "e => e.outerHTML"));
17+
assertEquals("<span></span>", page.evalOnSelector("text=/Hi\\>\\>/ >> span", "e => e.outerHTML"));
18+
19+
page.setContent("<div>let's<span>hello</span></div>");
20+
assertEquals("<span>hello</span>", page.evalOnSelector("text=/let's/i >> span", "e => e.outerHTML"));
21+
assertEquals("<span>hello</span>", page.evalOnSelector("text=/let\'s/i >> span", "e => e.outerHTML"));
22+
}
23+
1224
@Test
1325
void hasTextAndInternalTextShouldMatchFullNodeTextInStrictMode() {
1426
page.setContent("<div id=div1>hello<span>world</span></div>\n" +

scripts/CLI_VERSION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
1.38.0-alpha-sep-10-2023
1+
1.38.0

0 commit comments

Comments
 (0)