diff --git a/playwright/_impl/_browser_context.py b/playwright/_impl/_browser_context.py
index 4293f1220..ddb8ac41a 100644
--- a/playwright/_impl/_browser_context.py
+++ b/playwright/_impl/_browser_context.py
@@ -566,7 +566,7 @@ def _on_dialog(self, dialog: Dialog) -> None:
else:
asyncio.create_task(dialog.dismiss())
- async def _on_page_error(self, error: Error, page: Optional[Page]) -> None:
+ def _on_page_error(self, error: Error, page: Optional[Page]) -> None:
self.emit(BrowserContext.Events.WebError, WebError(self._loop, page, error))
if page:
page.emit(Page.Events.PageError, error)
diff --git a/playwright/_impl/_locator.py b/playwright/_impl/_locator.py
index 8c9a18f03..8a0c8282f 100644
--- a/playwright/_impl/_locator.py
+++ b/playwright/_impl/_locator.py
@@ -52,7 +52,6 @@
from playwright._impl._str_utils import (
escape_for_attribute_selector,
escape_for_text_selector,
- escape_regex_flags,
)
if sys.version_info >= (3, 8): # pragma: no cover
@@ -847,16 +846,12 @@ def set_test_id_attribute_name(attribute_name: str) -> None:
def get_by_test_id_selector(
test_id_attribute_name: str, test_id: Union[str, Pattern[str]]
) -> str:
- if isinstance(test_id, Pattern):
- return f"internal:testid=[{test_id_attribute_name}=/{test_id.pattern}/{escape_regex_flags(test_id)}]"
return f"internal:testid=[{test_id_attribute_name}={escape_for_attribute_selector(test_id, True)}]"
def get_by_attribute_text_selector(
attr_name: str, text: Union[str, Pattern[str]], exact: bool = None
) -> str:
- if isinstance(text, Pattern):
- return f"internal:attr=[{attr_name}=/{text.pattern}/{escape_regex_flags(text)}]"
return f"internal:attr=[{attr_name}={escape_for_attribute_selector(text, exact=exact)}]"
@@ -915,9 +910,7 @@ def get_by_role_selector(
props.append(
(
"name",
- f"/{name.pattern}/{escape_regex_flags(name)}"
- if isinstance(name, Pattern)
- else escape_for_attribute_selector(name, exact),
+ escape_for_attribute_selector(name, exact=exact),
)
)
if pressed is not None:
diff --git a/playwright/_impl/_str_utils.py b/playwright/_impl/_str_utils.py
index 769f530de..8b3e65a39 100644
--- a/playwright/_impl/_str_utils.py
+++ b/playwright/_impl/_str_utils.py
@@ -39,15 +39,31 @@ def escape_for_regex(text: str) -> str:
return re.sub(r"[.*+?^>${}()|[\]\\]", "\\$&", text)
+def escape_regex_for_selector(text: Pattern) -> str:
+ # Even number of backslashes followed by the quote -> insert a backslash.
+ return (
+ "/"
+ + re.sub(r'(^|[^\\])(\\\\)*(["\'`])', r"\1\2\\\3", text.pattern).replace(
+ ">>", "\\>\\>"
+ )
+ + "/"
+ + escape_regex_flags(text)
+ )
+
+
def escape_for_text_selector(
text: Union[str, Pattern[str]], exact: bool = None, case_sensitive: bool = None
) -> str:
if isinstance(text, Pattern):
- return f"/{text.pattern}/{escape_regex_flags(text)}"
+ return escape_regex_for_selector(text)
return json.dumps(text) + ("s" if exact else "i")
-def escape_for_attribute_selector(value: str, exact: bool = None) -> str:
+def escape_for_attribute_selector(
+ value: Union[str, Pattern], exact: bool = None
+) -> str:
+ if isinstance(value, Pattern):
+ return escape_regex_for_selector(value)
# TODO: this should actually be
# cssEscape(value).replace(/\\ /g, ' ')
# However, our attribute selectors do not conform to CSS parsing spec,
diff --git a/tests/async/test_selectors_get_by.py b/tests/async/test_selectors_get_by.py
index 1a07d1a9a..718264b62 100644
--- a/tests/async/test_selectors_get_by.py
+++ b/tests/async/test_selectors_get_by.py
@@ -12,6 +12,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+import re
+
from playwright.async_api import Page, expect
@@ -87,6 +89,42 @@ async def test_get_by_escaping(page: Page) -> None:
0, timeout=500
)
+ await page.set_content(
+ """"""
+ )
+ await page.eval_on_selector(
+ "input",
+ """input => {
+ input.setAttribute('placeholder', 'foo >> bar');
+ input.setAttribute('title', 'foo >> bar');
+ input.setAttribute('alt', 'foo >> bar');
+ }""",
+ )
+ assert await page.get_by_text("foo >> bar").text_content() == "foo >> bar"
+ await expect(page.locator("label")).to_have_text("foo >> bar")
+ await expect(page.get_by_text("foo >> bar")).to_have_text("foo >> bar")
+ assert (
+ await page.get_by_text(re.compile("foo >> bar")).text_content() == "foo >> bar"
+ )
+ await expect(page.get_by_label("foo >> bar")).to_have_attribute("id", "target")
+ await expect(page.get_by_label(re.compile("foo >> bar"))).to_have_attribute(
+ "id", "target"
+ )
+ await expect(page.get_by_placeholder("foo >> bar")).to_have_attribute(
+ "id", "target"
+ )
+ await expect(page.get_by_alt_text("foo >> bar")).to_have_attribute("id", "target")
+ await expect(page.get_by_title("foo >> bar")).to_have_attribute("id", "target")
+ await expect(page.get_by_placeholder(re.compile("foo >> bar"))).to_have_attribute(
+ "id", "target"
+ )
+ await expect(page.get_by_alt_text(re.compile("foo >> bar"))).to_have_attribute(
+ "id", "target"
+ )
+ await expect(page.get_by_title(re.compile("foo >> bar"))).to_have_attribute(
+ "id", "target"
+ )
+
async def test_get_by_role_escaping(
page: Page,
diff --git a/tests/async/test_selectors_text.py b/tests/async/test_selectors_text.py
index 1f09bdebd..9de7f6b4d 100644
--- a/tests/async/test_selectors_text.py
+++ b/tests/async/test_selectors_text.py
@@ -1,6 +1,8 @@
import re
-from playwright.async_api import Page, expect
+import pytest
+
+from playwright.async_api import Error, Page, expect
async def test_has_text_and_internal_text_should_match_full_node_text_in_strict_mode(
@@ -33,3 +35,236 @@ async def test_has_text_and_internal_text_should_match_full_node_text_in_strict_
"div1"
)
await expect(page.locator("div", has_text=re.compile("^hello$"))).to_have_id("div2")
+
+
+async def test_should_work(page: Page, server) -> None:
+ await page.set_content(
+ """
+
yo
ya
\nye
+ """
+ )
+ assert await page.eval_on_selector("text=ya", "e => e.outerHTML") == "ya
"
+ assert (
+ await page.eval_on_selector('text="ya"', "e => e.outerHTML") == "ya
"
+ )
+ assert (
+ await page.eval_on_selector("text=/^[ay]+$/", "e => e.outerHTML")
+ == "ya
"
+ )
+ assert (
+ await page.eval_on_selector("text=/Ya/i", "e => e.outerHTML") == "ya
"
+ )
+ assert (
+ await page.eval_on_selector("text=ye", "e => e.outerHTML")
+ == "\nye
"
+ )
+ assert ">\nye " in await page.get_by_text("ye").evaluate("e => e.outerHTML")
+
+ await page.set_content(
+ """
+ ye
ye
+ """
+ )
+ assert (
+ await page.eval_on_selector('text="ye"', "e => e.outerHTML")
+ == " ye
"
+ )
+ assert "> ye " in await page.get_by_text("ye", exact=True).first.evaluate(
+ "e => e.outerHTML"
+ )
+
+ await page.set_content(
+ """
+ yo
"ya
hello world!
+ """
+ )
+ assert (
+ await page.eval_on_selector('text="\\"ya"', "e => e.outerHTML")
+ == '"ya
'
+ )
+ assert (
+ await page.eval_on_selector("text=/hello/", "e => e.outerHTML")
+ == " hello world!
"
+ )
+ assert (
+ await page.eval_on_selector("text=/^\\s*heLLo/i", "e => e.outerHTML")
+ == " hello world!
"
+ )
+
+ await page.set_content(
+ """
+
+ """
+ )
+ assert (
+ await page.eval_on_selector("text=hey", "e => e.outerHTML") == "hey
"
+ )
+ assert (
+ await page.eval_on_selector('text=yo>>text="ya"', "e => e.outerHTML")
+ == "ya
"
+ )
+ assert (
+ await page.eval_on_selector('text=yo>> text="ya"', "e => e.outerHTML")
+ == "ya
"
+ )
+ assert (
+ await page.eval_on_selector("text=yo >>text='ya'", "e => e.outerHTML")
+ == "ya
"
+ )
+ assert (
+ await page.eval_on_selector("text=yo >> text='ya'", "e => e.outerHTML")
+ == "ya
"
+ )
+ assert (
+ await page.eval_on_selector("'yo'>>\"ya\"", "e => e.outerHTML")
+ == "ya
"
+ )
+ assert (
+ await page.eval_on_selector("\"yo\" >> 'ya'", "e => e.outerHTML")
+ == "ya
"
+ )
+
+ await page.set_content(
+ """
+ yo
yo
+ """
+ )
+ assert (
+ await page.eval_on_selector_all(
+ "text=yo", "es => es.map(e => e.outerHTML).join('\\n')"
+ )
+ == 'yo
\nyo
'
+ )
+
+ await page.set_content("'
\"
\\
x
")
+ assert (
+ await page.eval_on_selector("text='\\''", "e => e.outerHTML") == "'
"
+ )
+ assert (
+ await page.eval_on_selector("text='\"'", "e => e.outerHTML") == '"
'
+ )
+ assert (
+ await page.eval_on_selector('text="\\""', "e => e.outerHTML") == '"
'
+ )
+ assert (
+ await page.eval_on_selector('text="\'"', "e => e.outerHTML") == "'
"
+ )
+ assert (
+ await page.eval_on_selector('text="\\x"', "e => e.outerHTML") == "x
"
+ )
+ assert (
+ await page.eval_on_selector("text='\\x'", "e => e.outerHTML") == "x
"
+ )
+ assert (
+ await page.eval_on_selector("text='\\\\'", "e => e.outerHTML")
+ == "\\
"
+ )
+ assert (
+ await page.eval_on_selector('text="\\\\"', "e => e.outerHTML")
+ == "\\
"
+ )
+ assert await page.eval_on_selector('text="', "e => e.outerHTML") == '"
'
+ assert await page.eval_on_selector("text='", "e => e.outerHTML") == "'
"
+ assert await page.eval_on_selector('"x"', "e => e.outerHTML") == "x
"
+ assert await page.eval_on_selector("'x'", "e => e.outerHTML") == "x
"
+ with pytest.raises(Error):
+ await page.query_selector_all('"')
+ with pytest.raises(Error):
+ await page.query_selector_all("'")
+
+ await page.set_content(" '
\"
")
+ assert await page.eval_on_selector('text="', "e => e.outerHTML") == ' "
'
+ assert await page.eval_on_selector("text='", "e => e.outerHTML") == " '
"
+
+ await page.set_content("Hi''>>foo=bar
")
+ assert (
+ await page.eval_on_selector("text=\"Hi''>>foo=bar\"", "e => e.outerHTML")
+ == "Hi''>>foo=bar
"
+ )
+ await page.set_content("Hi'\">>foo=bar
")
+ assert (
+ await page.eval_on_selector('text="Hi\'\\">>foo=bar"', "e => e.outerHTML")
+ == "Hi'\">>foo=bar
"
+ )
+
+ await page.set_content("Hi>>
")
+ assert (
+ await page.eval_on_selector('text="Hi>>">>span', "e => e.outerHTML")
+ == ""
+ )
+ assert (
+ await page.eval_on_selector("text=/Hi\\>\\>/ >> span", "e => e.outerHTML")
+ == ""
+ )
+
+ await page.set_content("a
b
a
")
+ assert (
+ await page.eval_on_selector("text=a", "e => e.outerHTML") == "a
b
"
+ )
+ assert (
+ await page.eval_on_selector("text=b", "e => e.outerHTML") == "a
b
"
+ )
+ assert (
+ await page.eval_on_selector("text=ab", "e => e.outerHTML")
+ == "a
b
"
+ )
+ assert await page.query_selector("text=abc") is None
+ assert await page.eval_on_selector_all("text=a", "els => els.length") == 2
+ assert await page.eval_on_selector_all("text=b", "els => els.length") == 1
+ assert await page.eval_on_selector_all("text=ab", "els => els.length") == 1
+ assert await page.eval_on_selector_all("text=abc", "els => els.length") == 0
+
+ await page.set_content("")
+ await page.eval_on_selector(
+ "div",
+ """div => {
+ div.appendChild(document.createTextNode('hello'))
+ div.appendChild(document.createTextNode('world'))
+ }""",
+ )
+ await page.eval_on_selector(
+ "span",
+ """span => {
+ span.appendChild(document.createTextNode('hello'))
+ span.appendChild(document.createTextNode('world'))
+ }""",
+ )
+ assert (
+ await page.eval_on_selector("text=lowo", "e => e.outerHTML")
+ == "helloworld
"
+ )
+ assert (
+ await page.eval_on_selector_all(
+ "text=lowo", "els => els.map(e => e.outerHTML).join('')"
+ )
+ == "helloworld
helloworld"
+ )
+
+ await page.set_content("Sign inHello\n \nworld")
+ assert (
+ await page.eval_on_selector("text=Sign in", "e => e.outerHTML")
+ == "Sign in"
+ )
+ assert len((await page.query_selector_all("text=Sign \tin"))) == 1
+ assert len((await page.query_selector_all('text="Sign in"'))) == 1
+ assert (
+ await page.eval_on_selector("text=lo wo", "e => e.outerHTML")
+ == "Hello\n \nworld"
+ )
+ assert (
+ await page.eval_on_selector('text="Hello world"', "e => e.outerHTML")
+ == "Hello\n \nworld"
+ )
+ assert await page.query_selector('text="lo wo"') is None
+ assert len((await page.query_selector_all("text=lo \nwo"))) == 1
+ assert len(await page.query_selector_all('text="lo \nwo"')) == 0
+
+ await page.set_content("let'shello
")
+ assert (
+ await page.eval_on_selector("text=/let's/i >> span", "e => e.outerHTML")
+ == "hello"
+ )
+ assert (
+ await page.eval_on_selector("text=/let\\'s/i >> span", "e => e.outerHTML")
+ == "hello"
+ )