Skip to content
Open
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
4 changes: 2 additions & 2 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 -->140.0.7339.16<!-- GEN:stop --> | ✅ | ✅ | ✅ |
| Chromium <!-- GEN:chromium-version -->141.0.7390.37<!-- GEN:stop --> | ✅ | ✅ | ✅ |
| WebKit <!-- GEN:webkit-version -->26.0<!-- GEN:stop --> | ✅ | ✅ | ✅ |
| Firefox <!-- GEN:firefox-version -->141.0<!-- GEN:stop --> | ✅ | ✅ | ✅ |
| Firefox <!-- GEN:firefox-version -->142.0.1<!-- GEN:stop --> | ✅ | ✅ | ✅ |

## Documentation

Expand Down
1 change: 1 addition & 0 deletions playwright/_impl/_api_structures.py
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,7 @@ class FrameExpectResult(TypedDict):
matches: bool
received: Any
log: List[str]
errorMessage: Optional[str]


AriaRole = Literal[
Expand Down
4 changes: 3 additions & 1 deletion playwright/_impl/_assertions.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,10 @@ async def _expect_impl(
out_message = (
f"{message} '{expected}'" if expected is not None else f"{message}"
)
error_message = result.get("errorMessage")
error_message = f"\n{error_message}" if error_message else ""
raise AssertionError(
f"{out_message}\nActual value: {actual} {format_call_log(result.get('log'))}"
f"{out_message}\nActual value: {actual}{error_message} {format_call_log(result.get('log'))}"
)


Expand Down
12 changes: 2 additions & 10 deletions playwright/_impl/_browser_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@

class BrowserContext(ChannelOwner):
Events = SimpleNamespace(
# Deprecated in v1.56, never emitted anymore.
BackgroundPage="backgroundpage",
Close="close",
Console="console",
Expand Down Expand Up @@ -117,7 +118,6 @@ def __init__(
self._timeout_settings = TimeoutSettings(None)
self._owner_page: Optional[Page] = None
self._options: Dict[str, Any] = initializer["options"]
self._background_pages: Set[Page] = set()
self._service_workers: Set[Worker] = set()
self._base_url: Optional[str] = self._options.get("baseURL")
self._videos_dir: Optional[str] = self._options.get("recordVideo")
Expand Down Expand Up @@ -149,10 +149,6 @@ def __init__(
)
),
)
self._channel.on(
"backgroundPage",
lambda params: self._on_background_page(from_channel(params["page"])),
)

self._channel.on(
"serviceWorker",
Expand Down Expand Up @@ -658,10 +654,6 @@ def expect_page(
) -> EventContextManagerImpl[Page]:
return self.expect_event(BrowserContext.Events.Page, predicate, timeout)

def _on_background_page(self, page: Page) -> None:
self._background_pages.add(page)
self.emit(BrowserContext.Events.BackgroundPage, page)

def _on_service_worker(self, worker: Worker) -> None:
worker._context = self
self._service_workers.add(worker)
Expand Down Expand Up @@ -736,7 +728,7 @@ def _on_response(self, response: Response, page: Optional[Page]) -> None:

@property
def background_pages(self) -> List[Page]:
return list(self._background_pages)
return []

@property
def service_workers(self) -> List[Worker]:
Expand Down
12 changes: 2 additions & 10 deletions playwright/_impl/_glob.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,20 +28,12 @@ def glob_to_regex_pattern(glob: str) -> str:
tokens.append("\\" + char if char in escaped_chars else char)
i += 1
elif c == "*":
before_deep = glob[i - 1] if i > 0 else None
star_count = 1
while i + 1 < len(glob) and glob[i + 1] == "*":
star_count += 1
i += 1
after_deep = glob[i + 1] if i + 1 < len(glob) else None
is_deep = (
star_count > 1
and (before_deep == "/" or before_deep is None)
and (after_deep == "/" or after_deep is None)
)
if is_deep:
tokens.append("((?:[^/]*(?:/|$))*)")
i += 1
if star_count > 1:
tokens.append("(.*)")
else:
tokens.append("([^/]*)")
else:
Expand Down
20 changes: 18 additions & 2 deletions playwright/_impl/_page.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@
async_writefile,
locals_to_params,
make_dirs_for_file,
parse_error,
serialize_error,
url_matches,
)
Expand Down Expand Up @@ -344,8 +345,6 @@ def _on_close(self) -> None:
self._is_closed = True
if self in self._browser_context._pages:
self._browser_context._pages.remove(self)
if self in self._browser_context._background_pages:
self._browser_context._background_pages.remove(self)
self._dispose_har_routers()
self.emit(Page.Events.Close, self)

Expand Down Expand Up @@ -1434,6 +1433,23 @@ async def remove_locator_handler(self, locator: "Locator") -> None:
{"uid": uid},
)

async def requests(self) -> List[Request]:
request_objects = await self._channel.send("requests", None)
return [from_channel(r) for r in request_objects]

async def console_messages(self) -> List[ConsoleMessage]:
message_dicts = await self._channel.send("consoleMessages", None)
return [
ConsoleMessage(
{**event, "page": self._channel}, self._loop, self._dispatcher_fiber
)
for event in message_dicts
]

async def page_errors(self) -> List[Error]:
error_objects = await self._channel.send("pageErrors", None)
return [parse_error(error["error"]) for error in error_objects]


class Worker(ChannelOwner):
Events = SimpleNamespace(Close="close")
Expand Down
65 changes: 47 additions & 18 deletions playwright/async_api/_generated.py
Original file line number Diff line number Diff line change
Expand Up @@ -12254,6 +12254,49 @@ async def remove_locator_handler(self, locator: "Locator") -> None:
await self._impl_obj.remove_locator_handler(locator=locator._impl_obj)
)

async def requests(self) -> typing.List["Request"]:
"""Page.requests

Returns up to (currently) 100 last network request from this page. See `page.on('request')` for more details.

Returned requests should be accessed immediately, otherwise they might be collected to prevent unbounded memory
growth as new requests come in. Once collected, retrieving most information about the request is impossible.

Note that requests reported through the `page.on('request')` request are not collected, so there is a trade off
between efficient memory usage with `page.requests()` and the amount of available information reported
through `page.on('request')`.

Returns
-------
List[Request]
"""

return mapping.from_impl_list(await self._impl_obj.requests())

async def console_messages(self) -> typing.List["ConsoleMessage"]:
"""Page.console_messages

Returns up to (currently) 200 last console messages from this page. See `page.on('console')` for more details.

Returns
-------
List[ConsoleMessage]
"""

return mapping.from_impl_list(await self._impl_obj.console_messages())

async def page_errors(self) -> typing.List["Error"]:
"""Page.page_errors

Returns up to (currently) 200 last page errors from this page. See `page.on('page_error')` for more details.

Returns
-------
List[Error]
"""

return mapping.from_impl_list(await self._impl_obj.page_errors())


mapping.register(PageImpl, Page)

Expand Down Expand Up @@ -12297,13 +12340,7 @@ def on(
f: typing.Callable[["Page"], "typing.Union[typing.Awaitable[None], None]"],
) -> None:
"""
**NOTE** Only works with Chromium browser's persistent context.

Emitted when new background page is created in the context.

```py
background_page = await context.wait_for_event(\"backgroundpage\")
```"""
This event is not emitted."""

@typing.overload
def on(
Expand Down Expand Up @@ -12477,13 +12514,7 @@ def once(
f: typing.Callable[["Page"], "typing.Union[typing.Awaitable[None], None]"],
) -> None:
"""
**NOTE** Only works with Chromium browser's persistent context.

Emitted when new background page is created in the context.

```py
background_page = await context.wait_for_event(\"backgroundpage\")
```"""
This event is not emitted."""

@typing.overload
def once(
Expand Down Expand Up @@ -12679,9 +12710,7 @@ def browser(self) -> typing.Optional["Browser"]:
def background_pages(self) -> typing.List["Page"]:
"""BrowserContext.background_pages

**NOTE** Background pages are only supported on Chromium-based browsers.

All existing background pages in the context.
Returns an empty list.

Returns
-------
Expand Down Expand Up @@ -16617,7 +16646,7 @@ def and_(self, locator: "Locator") -> "Locator":
The following example finds a button with a specific title.

```py
button = page.get_by_role(\"button\").and_(page.getByTitle(\"Subscribe\"))
button = page.get_by_role(\"button\").and_(page.get_by_title(\"Subscribe\"))
```

Parameters
Expand Down
65 changes: 47 additions & 18 deletions playwright/sync_api/_generated.py
Original file line number Diff line number Diff line change
Expand Up @@ -12342,6 +12342,49 @@ def remove_locator_handler(self, locator: "Locator") -> None:
self._sync(self._impl_obj.remove_locator_handler(locator=locator._impl_obj))
)

def requests(self) -> typing.List["Request"]:
"""Page.requests

Returns up to (currently) 100 last network request from this page. See `page.on('request')` for more details.

Returned requests should be accessed immediately, otherwise they might be collected to prevent unbounded memory
growth as new requests come in. Once collected, retrieving most information about the request is impossible.

Note that requests reported through the `page.on('request')` request are not collected, so there is a trade off
between efficient memory usage with `page.requests()` and the amount of available information reported
through `page.on('request')`.

Returns
-------
List[Request]
"""

return mapping.from_impl_list(self._sync(self._impl_obj.requests()))

def console_messages(self) -> typing.List["ConsoleMessage"]:
"""Page.console_messages

Returns up to (currently) 200 last console messages from this page. See `page.on('console')` for more details.

Returns
-------
List[ConsoleMessage]
"""

return mapping.from_impl_list(self._sync(self._impl_obj.console_messages()))

def page_errors(self) -> typing.List["Error"]:
"""Page.page_errors

Returns up to (currently) 200 last page errors from this page. See `page.on('page_error')` for more details.

Returns
-------
List[Error]
"""

return mapping.from_impl_list(self._sync(self._impl_obj.page_errors()))


mapping.register(PageImpl, Page)

Expand Down Expand Up @@ -12383,13 +12426,7 @@ def on(
self, event: Literal["backgroundpage"], f: typing.Callable[["Page"], "None"]
) -> None:
"""
**NOTE** Only works with Chromium browser's persistent context.

Emitted when new background page is created in the context.

```py
background_page = context.wait_for_event(\"backgroundpage\")
```"""
This event is not emitted."""

@typing.overload
def on(
Expand Down Expand Up @@ -12529,13 +12566,7 @@ def once(
self, event: Literal["backgroundpage"], f: typing.Callable[["Page"], "None"]
) -> None:
"""
**NOTE** Only works with Chromium browser's persistent context.

Emitted when new background page is created in the context.

```py
background_page = context.wait_for_event(\"backgroundpage\")
```"""
This event is not emitted."""

@typing.overload
def once(
Expand Down Expand Up @@ -12701,9 +12732,7 @@ def browser(self) -> typing.Optional["Browser"]:
def background_pages(self) -> typing.List["Page"]:
"""BrowserContext.background_pages

**NOTE** Background pages are only supported on Chromium-based browsers.

All existing background pages in the context.
Returns an empty list.

Returns
-------
Expand Down Expand Up @@ -16680,7 +16709,7 @@ def and_(self, locator: "Locator") -> "Locator":
The following example finds a button with a specific title.

```py
button = page.get_by_role(\"button\").and_(page.getByTitle(\"Subscribe\"))
button = page.get_by_role(\"button\").and_(page.get_by_title(\"Subscribe\"))
```

Parameters
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
import zipfile
from typing import Dict

driver_version = "1.55.0-beta-1756314050000"
driver_version = "1.56.0-beta-1759412259000"

base_wheel_bundles = [
{
Expand Down
8 changes: 4 additions & 4 deletions tests/async/test_assertions.py
Original file line number Diff line number Diff line change
Expand Up @@ -516,7 +516,7 @@ async def test_to_have_values_fails_when_multiple_not_specified(
)
locator = page.locator("select")
await locator.select_option(["B"])
with pytest.raises(Error) as excinfo:
with pytest.raises(AssertionError) as excinfo:
await expect(locator).to_have_values(["R", "G"], timeout=500)
assert "Error: Not a select element with a multiple attribute" in str(excinfo.value)

Expand All @@ -530,7 +530,7 @@ async def test_to_have_values_fails_when_not_a_select_element(
"""
)
locator = page.locator("input")
with pytest.raises(Error) as excinfo:
with pytest.raises(AssertionError) as excinfo:
await expect(locator).to_have_values(["R", "G"], timeout=500)
assert "Error: Not a select element with a multiple attribute" in str(excinfo.value)

Expand Down Expand Up @@ -564,7 +564,7 @@ async def test_assertions_boolean_checked_with_intermediate_true_and_checked(
await page.set_content("<input type=checkbox></input>")
await page.locator("input").evaluate("e => e.indeterminate = true")
with pytest.raises(
Error, match="Can't assert indeterminate and checked at the same time"
AssertionError, match="Can't assert indeterminate and checked at the same time"
):
await expect(page.locator("input")).to_be_checked(
checked=False, indeterminate=True
Expand Down Expand Up @@ -658,7 +658,7 @@ async def test_assertions_locator_to_be_editable_throws(
await page.goto(server.EMPTY_PAGE)
await page.set_content("<button disabled>Text</button>")
with pytest.raises(
Error,
AssertionError,
match=r"Element is not an <input>, <textarea>, <select> or \[contenteditable\] and does not have a role allowing \[aria-readonly\]",
):
await expect(page.locator("button")).not_to_be_editable()
Expand Down
2 changes: 1 addition & 1 deletion tests/async/test_console.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ async def test_console_should_trigger_correct_log(page: Page, server: Server) ->
async with page.expect_console_message() as message_info:
await page.evaluate("async url => fetch(url).catch(e => {})", server.EMPTY_PAGE)
message = await message_info.value
assert "Access-Control-Allow-Origin" in message.text
assert "Access-Control-Allow-Origin" in message.text or "CORS" in message.text
assert message.type == "error"


Expand Down
Loading
Loading