Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion playwright/_impl/_js_handle.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,17 @@
import collections.abc
import datetime
import math
import re
import struct
import traceback
from pathlib import Path
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Union
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Pattern, Union
from urllib.parse import ParseResult, urlparse, urlunparse

from playwright._impl._connection import Channel, ChannelOwner, from_channel
from playwright._impl._errors import Error, is_target_closed_error
from playwright._impl._map import Map
from playwright._impl._str_utils import escape_regex_flags, parse_regex_flags

if TYPE_CHECKING: # pragma: no cover
from playwright._impl._element_handle import ElementHandle
Expand Down Expand Up @@ -182,6 +184,9 @@ def serialize_value(
return {"s": value}
if isinstance(value, ParseResult):
return {"u": urlunparse(value)}
if isinstance(value, Pattern):
assert isinstance(value.pattern, str), "Only str patterns are supported."
return {"r": {"p": value.pattern, "f": escape_regex_flags(value)}}

if value in visitor_info.visited:
return dict(ref=visitor_info.visited[value])
Expand Down Expand Up @@ -247,6 +252,13 @@ def parse_value(value: Any, refs: Optional[Dict[int, Any]] = None) -> Any:
error._stack = value["e"]["s"]
return error

if "r" in value:
try:
return re.compile(value["r"]["p"], parse_regex_flags(value["r"]["f"]))
except re.error:
# JavaScript-only syntax, e.g. /(?<name>x)/, has no re equivalent.
return value

if "a" in value:
a: List = []
refs[value["id"]] = a
Expand Down
12 changes: 12 additions & 0 deletions playwright/_impl/_str_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,18 @@ def escape_regex_flags(pattern: Pattern) -> str:
return flags


def parse_regex_flags(flags: str) -> int:
result = 0
if "i" in flags:
result |= re.IGNORECASE
if "s" in flags:
result |= re.DOTALL
if "m" in flags:
result |= re.MULTILINE
# The remaining JavaScript flags (d, g, u, v, y) have no re equivalent.
return result


def escape_for_regex(text: str) -> str:
return re.sub(r"[.*+?^>${}()|[\]\\]", "\\$&", text)

Expand Down
43 changes: 43 additions & 0 deletions tests/async/test_page_evaluate.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,13 @@
# limitations under the License.

import math
import re
from datetime import datetime, timedelta, timezone
from typing import Optional
from urllib.parse import ParseResult, urlparse

import pytest

from playwright.async_api import Error, Page


Expand Down Expand Up @@ -358,3 +361,43 @@ async def test_evaluate_jsonvalue_url(page: Page) -> None:
url = urlparse("https://example.com/")
result = await page.evaluate('() => ({ someKey: new URL("https://example.com/") })')
assert result == {"someKey": url}


async def test_evaluate_transfer_regex(page: Page) -> None:
regex = re.compile("foo.bar", re.IGNORECASE | re.MULTILINE)
assert await page.evaluate("a => a", regex) == regex


async def test_evaluate_pass_regex_as_regexp(page: Page) -> None:
result = await page.evaluate(
"r => [r instanceof RegExp, r.source, r.flags, r.test('A12')]",
re.compile(r"a\d+", re.IGNORECASE | re.DOTALL | re.MULTILINE),
)
assert result == [True, "a\\d+", "ims", True]


async def test_evaluate_return_regex(page: Page) -> None:
result = await page.evaluate(
"() => ({ list: [/foo/s], nested: { re: /a\\d+/gi } })"
)
assert result == {
"list": [re.compile("foo", re.DOTALL)],
"nested": {"re": re.compile(r"a\d+", re.IGNORECASE)},
}


async def test_evaluate_jsonvalue_regex(page: Page) -> None:
handle = await page.evaluate_handle("() => ({ someKey: /foo/i })")
assert await handle.json_value() == {"someKey": re.compile("foo", re.IGNORECASE)}


async def test_evaluate_return_regex_unsupported_by_python(page: Page) -> None:
result = await page.evaluate("() => ({ a: 1, re: /(?<year>\\d{4})/ })")
assert result == {"a": 1, "re": {"r": {"p": "(?<year>\\d{4})", "f": ""}}}


async def test_evaluate_throw_for_unsupported_regex(page: Page) -> None:
with pytest.raises(AssertionError, match="Unexpected re.Pattern flag"):
await page.evaluate("a => a", re.compile("foo", re.VERBOSE))
with pytest.raises(AssertionError, match="Only str patterns are supported"):
await page.evaluate("a => a", re.compile(b"foo"))
Loading