Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ Playwright is a Python library to automate [Chromium](https://www.chromium.org/H

| | Linux | macOS | Windows |
| :--- | :---: | :---: | :---: |
| Chromium <!-- GEN:chromium-version -->104.0.5112.20<!-- GEN:stop --> | ✅ | ✅ | ✅ |
| WebKit <!-- GEN:webkit-version -->15.4<!-- GEN:stop --> | ✅ | ✅ | ✅ |
| Firefox <!-- GEN:firefox-version -->100.0.2<!-- GEN:stop --> | ✅ | ✅ | ✅ |
| Chromium <!-- GEN:chromium-version -->104.0.5112.48<!-- GEN:stop --> | ✅ | ✅ | ✅ |
| WebKit <!-- GEN:webkit-version -->16.0<!-- GEN:stop --> | ✅ | ✅ | ✅ |
| Firefox <!-- GEN:firefox-version -->102.0<!-- GEN:stop --> | ✅ | ✅ | ✅ |

## Documentation

Expand Down
6 changes: 6 additions & 0 deletions playwright/_impl/_js_handle.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
from dataclasses import dataclass
from datetime import datetime
from typing import TYPE_CHECKING, Any, Dict, List, Optional
from urllib.parse import ParseResult, urlparse, urlunparse

from playwright._impl._connection import ChannelOwner, from_channel
from playwright._impl._map import Map
Expand Down Expand Up @@ -129,6 +130,8 @@ def serialize_value(
return {"n": value}
if isinstance(value, str):
return {"s": value}
if isinstance(value, ParseResult):
return {"u": urlunparse(value)}

if value in visitor_info.visited:
return dict(ref=visitor_info.visited[value])
Expand Down Expand Up @@ -180,6 +183,9 @@ def parse_value(value: Any, refs: Dict[int, Any] = {}) -> Any:
return None
return v

if "u" in value:
return urlparse(value["u"])

if "a" in value:
a: List = []
refs[value["id"]] = a
Expand Down
5 changes: 2 additions & 3 deletions playwright/_impl/_locator.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,9 +74,8 @@ def __init__(

if has_text:
if isinstance(has_text, Pattern):
pattern = escape_with_quotes(has_text.pattern, '"')
flags = escape_regex_flags(has_text)
self._selector += f' >> :scope:text-matches({pattern}, "{flags}")'
js_regex = f"/{has_text.pattern}/{escape_regex_flags(has_text)}"
self._selector += f' >> has={json.dumps("text=" + js_regex)}'
else:
escaped = escape_with_quotes(has_text, '"')
self._selector += f" >> :scope:has-text({escaped})"
Expand Down
4 changes: 4 additions & 0 deletions playwright/_impl/_network.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,10 @@ def method(self) -> str:
str, self._fallback_overrides.get("method", self._initializer["method"])
)

@property
def service_worker(self) -> None:
pass

async def sizes(self) -> RequestSizes:
response = await self.response()
if not response:
Expand Down
57 changes: 42 additions & 15 deletions playwright/async_api/_generated.py
Original file line number Diff line number Diff line change
Expand Up @@ -706,7 +706,7 @@ async def fallback(
"""Route.fallback

When several routes match the given pattern, they run in the order opposite to their registration. That way the last
registered route can always override all the previos ones. In the example below, request will be handled by the
registered route can always override all the previous ones. In the example below, request will be handled by the
bottom-most handler first, then it'll fall back to the previous one and in the end will be aborted by the first
registered route.

Expand Down Expand Up @@ -2426,7 +2426,7 @@ async def bounding_box(self) -> typing.Optional[FloatRect]:
This method returns the bounding box of the element, or `null` if the element is not visible. The bounding box is
calculated relative to the main frame viewport - which is usually the same as the browser window.

Scrolling affects the returned bonding box, similarly to
Scrolling affects the returned bounding box, similarly to
[Element.getBoundingClientRect](https://developer.mozilla.org/en-US/docs/Web/API/Element/getBoundingClientRect). That
means `x` and/or `y` may be negative.

Expand Down Expand Up @@ -4451,7 +4451,11 @@ async def drag_and_drop(
Parameters
----------
source : str
A selector to search for an element to drag. If there are multiple elements satisfying the selector, the first will be
used. See [working with selectors](../selectors.md) for more details.
target : str
A selector to search for an element to drop onto. If there are multiple elements satisfying the selector, the first will
be used. See [working with selectors](../selectors.md) for more details.
source_position : Union[{x: float, y: float}, NoneType]
Clicks on the source element at this point relative to the top-left corner of the element's padding box. If not
specified, some visible point of the element is used.
Expand Down Expand Up @@ -5302,7 +5306,7 @@ async def run(playwright):
# Use the selector prefixed with its name.
button = await page.query_selector('tag=button')
# Combine it with other selector engines.
await page.click('tag=div >> text=\"Click me\"')
await page.locator('tag=div >> text=\"Click me\"').click()
# Can use it in any methods supporting selectors.
button_count = await page.locator('tag=button').count()
print(button_count)
Expand Down Expand Up @@ -7764,7 +7768,7 @@ async def route_from_har(
relative path, then it is resolved relative to the current working directory.
url : Union[Pattern, str, NoneType]
A glob pattern, regular expression or predicate to match the request URL. Only requests with URL matching the pattern
will be surved from the HAR file. If not specified, all requests are served from the HAR file.
will be served from the HAR file. If not specified, all requests are served from the HAR file.
not_found : Union["abort", "fallback", NoneType]
- If set to 'abort' any request not found in the HAR file will be aborted.
- If set to 'fallback' missing requests will be sent to the network.
Expand Down Expand Up @@ -8498,7 +8502,11 @@ async def drag_and_drop(
Parameters
----------
source : str
A selector to search for an element to drag. If there are multiple elements satisfying the selector, the first will be
used. See [working with selectors](../selectors.md) for more details.
target : str
A selector to search for an element to drop onto. If there are multiple elements satisfying the selector, the first will
be used. See [working with selectors](../selectors.md) for more details.
source_position : Union[{x: float, y: float}, NoneType]
Clicks on the source element at this point relative to the top-left corner of the element's padding box. If not
specified, some visible point of the element is used.
Expand Down Expand Up @@ -9723,7 +9731,7 @@ def on(

```py
async with context.expect_page() as page_info:
await page.click(\"a[target=_blank]\"),
await page.locator(\"a[target=_blank]\").click(),
page = await page_info.value
print(await page.evaluate(\"location.href\"))
```
Expand Down Expand Up @@ -9841,7 +9849,7 @@ def once(

```py
async with context.expect_page() as page_info:
await page.click(\"a[target=_blank]\"),
await page.locator(\"a[target=_blank]\").click(),
page = await page_info.value
print(await page.evaluate(\"location.href\"))
```
Expand Down Expand Up @@ -10269,7 +10277,7 @@ async def run(playwright):
<button onclick=\"onClick()\">Click me</button>
<div></div>
\"\"\")
await page.click(\"button\")
await page.locator(\"button\").click()

async def main():
async with async_playwright() as playwright:
Expand Down Expand Up @@ -10347,7 +10355,7 @@ async def run(playwright):
<button onclick=\"onClick()\">Click me</button>
<div></div>
\"\"\")
await page.click(\"button\")
await page.locator(\"button\").click()

async def main():
async with async_playwright() as playwright:
Expand Down Expand Up @@ -10500,7 +10508,7 @@ async def route_from_har(
relative path, then it is resolved relative to the current working directory.
url : Union[Pattern, str, NoneType]
A glob pattern, regular expression or predicate to match the request URL. Only requests with URL matching the pattern
will be surved from the HAR file. If not specified, all requests are served from the HAR file.
will be served from the HAR file. If not specified, all requests are served from the HAR file.
not_found : Union["abort", "fallback", NoneType]
- If set to 'abort' any request not found in the HAR file will be aborted.
- If set to 'fallback' falls through to the next route handler in the handler chain.
Expand All @@ -10526,7 +10534,7 @@ def expect_event(

```py
async with context.expect_event(\"page\") as event_info:
await page.click(\"button\")
await page.locator(\"button\").click()
page = await event_info.value
```

Expand Down Expand Up @@ -10823,13 +10831,22 @@ async def new_context(

Creates a new browser context. It won't share cookies/cache with other browser contexts.

> NOTE: If directly using this method to create `BrowserContext`s, it is best practice to explicilty close the returned
context via `browser_context.close()` when your code is done with the `BrowserContext`, and before calling
`browser.close()`. This will ensure the `context` is closed gracefully and any artifacts—like HARs and
videos—are fully flushed and saved.

```py
browser = await playwright.firefox.launch() # or \"chromium\" or \"webkit\".
# create a new incognito browser context.
context = await browser.new_context()
# create a new page in a pristine context.
page = await context.new_page()
await page.goto(\"https://example.com\")

# gracefully close up everything
await context.close()
await browser.close()
```

Parameters
Expand Down Expand Up @@ -11181,6 +11198,10 @@ async def close(self) -> NoneType:
In case this browser is connected to, clears all created contexts belonging to this browser and disconnects from the
browser server.

> NOTE: This is similar to force quitting the browser. Therefore, you should call `browser_context.close()` on
any `BrowserContext`'s you explicitly created earlier with `browser.new_context()` **before** calling
`browser.close()`.

The `Browser` object itself is considered to be disposed and cannot be used anymore.
"""

Expand Down Expand Up @@ -11950,7 +11971,7 @@ async def start_chunk(self, *, title: str = None) -> NoneType:
await page.goto(\"https://playwright.dev\")

await context.tracing.start_chunk()
await page.click(\"text=Get Started\")
await page.locator(\"text=Get Started\").click()
# Everything between start_chunk and stop_chunk will be recorded in the trace.
await context.tracing.stop_chunk(path = \"trace1.zip\")

Expand Down Expand Up @@ -12045,7 +12066,7 @@ async def bounding_box(
This method returns the bounding box of the element, or `null` if the element is not visible. The bounding box is
calculated relative to the main frame viewport - which is usually the same as the browser window.

Scrolling affects the returned bonding box, similarly to
Scrolling affects the returned bounding box, similarly to
[Element.getBoundingClientRect](https://developer.mozilla.org/en-US/docs/Web/API/Element/getBoundingClientRect). That
means `x` and/or `y` may be negative.

Expand Down Expand Up @@ -14301,7 +14322,7 @@ async def to_have_url(
Parameters
----------
url_or_reg_exp : Union[Pattern, str]
Expected substring or RegExp.
Expected URL string or RegExp.
timeout : Union[float, NoneType]
Time to retry the assertion for.
"""
Expand All @@ -14326,7 +14347,7 @@ async def not_to_have_url(
Parameters
----------
url_or_reg_exp : Union[Pattern, str]
Expected substring or RegExp.
Expected URL string or RegExp.
timeout : Union[float, NoneType]
Time to retry the assertion for.
"""
Expand Down Expand Up @@ -14510,13 +14531,19 @@ async def to_have_class(
) -> NoneType:
"""LocatorAssertions.to_have_class

Ensures the `Locator` points to an element with given CSS class.
Ensures the `Locator` points to an element with given CSS classes. This needs to be a full match or using a relaxed
regular expression.

```html
<div class='selected row' id='component'></div>
```

```py
from playwright.async_api import expect

locator = page.locator(\"#component\")
await expect(locator).to_have_class(re.compile(r\"selected\"))
await expect(locator).to_have_class(\"selected row\")
```

Note that if array is passed as an expected value, entire lists of elements can be asserted:
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
InWheel = None
from wheel.bdist_wheel import bdist_wheel as BDistWheelCommand

driver_version = "1.23.1"
driver_version = "1.24.0-beta-1657919681000"


def extractall(zip: zipfile.ZipFile, path: str) -> None:
Expand Down
35 changes: 35 additions & 0 deletions tests/async/test_evaluate.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

import math
from datetime import datetime
from urllib.parse import ParseResult, urlparse

from playwright.async_api import Error

Expand Down Expand Up @@ -218,3 +219,37 @@ async def test_evaluate_jsonvalue_date(page):
'() => ({ date: new Date("2020-05-27T01:31:38.506Z") })'
)
assert result == {"date": date}


async def test_should_evaluate_url(page):
out = await page.evaluate(
"() => ({ someKey: new URL('https://user:[email protected]/?foo=bar#hi') })"
)
assert out["someKey"] == ParseResult(
scheme="https",
netloc="user:[email protected]",
path="/",
query="foo=bar",
params="",
fragment="hi",
)


async def test_should_roundtrip_url(page):
in_ = urlparse("https://user:[email protected]/?foo=bar#hi")
out = await page.evaluate("url => url", in_)
assert in_ == out


async def test_should_roundtrip_complex_url(page):
in_ = urlparse(
"https://user:[email protected]:80/Home/Index.htm?q1=v1&q2=v2#FragmentName"
)
out = await page.evaluate("url => url", in_)
assert in_ == out


async def test_evaluate_jsonvalue_url(page):
url = urlparse("https://example.com/")
result = await page.evaluate('() => ({ someKey: new URL("https://example.com/") })')
assert result == {"someKey": url}
25 changes: 25 additions & 0 deletions tests/async/test_locators.py
Original file line number Diff line number Diff line change
Expand Up @@ -784,3 +784,28 @@ async def test_should_support_locator_that(page: Page) -> None:
has_text="world",
)
).to_have_count(1)


async def test_should_filter_by_case_insensitive_regex_in_multiple_children(
page: Page,
) -> None:
await page.set_content(
'<div class="test"><h5>Title</h5> <h2><i>Text</i></h2></div>'
)
await expect(
page.locator("div", has_text=re.compile(r"^title text$", re.IGNORECASE))
).to_have_class("test")


async def test_should_filter_by_regex_with_special_symbols(
page: Page,
) -> None:
await page.set_content(
'<div class="test"><h5>First/"and"</h5><h2><i>Second\\</i></h2></div>'
)
await expect(
page.locator("div", has_text=re.compile(r'^first\/".*"second\\$', re.S | re.I))
).to_have_class("test")


# });