Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore(roll): roll Playwright to 1.40.0-alpha-oct-18-2023 #2167

Merged
merged 1 commit into from
Nov 16, 2023
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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ Playwright is a Python library to automate [Chromium](https://www.chromium.org/H

| | Linux | macOS | Windows |
| :--- | :---: | :---: | :---: |
| Chromium <!-- GEN:chromium-version -->119.0.6045.9<!-- GEN:stop --> ||||
| Chromium <!-- GEN:chromium-version -->119.0.6045.21<!-- GEN:stop --> ||||
| WebKit <!-- GEN:webkit-version -->17.4<!-- GEN:stop --> ||||
| Firefox <!-- GEN:firefox-version -->118.0.1<!-- GEN:stop --> ||||

Expand Down
4 changes: 2 additions & 2 deletions playwright/_impl/_artifact.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,13 @@ def __init__(
super().__init__(parent, type, guid, initializer)
self.absolute_path = initializer["absolutePath"]

async def path_after_finished(self) -> Optional[pathlib.Path]:
async def path_after_finished(self) -> pathlib.Path:
if self._connection.is_remote:
raise Error(
"Path is not available when using browser_type.connect(). Use save_as() to save a local copy."
)
path = await self._channel.send("pathAfterFinished")
return pathlib.Path(path) if path else None
return pathlib.Path(path)

async def save_as(self, path: Union[str, Path]) -> None:
stream = cast(Stream, from_channel(await self._channel.send("saveAsStream")))
Expand Down
13 changes: 5 additions & 8 deletions playwright/_impl/_assertions.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
from urllib.parse import urljoin

from playwright._impl._api_structures import ExpectedTextValue, FrameExpectOptions
from playwright._impl._connection import format_call_log
from playwright._impl._fetch import APIResponse
from playwright._impl._helper import is_textual_mime_type
from playwright._impl._locator import Locator
Expand Down Expand Up @@ -56,9 +57,6 @@ async def _expect_impl(
result = await self._actual_locator._expect(expression, expect_options)
if result["matches"] == self._is_not:
actual = result.get("received")
log = "\n".join(result.get("log", "")).strip()
if log:
log = "\nCall log:\n" + log
if self._custom_message:
out_message = self._custom_message
if expected is not None:
Expand All @@ -67,7 +65,9 @@ async def _expect_impl(
out_message = (
f"{message} '{expected}'" if expected is not None else f"{message}"
)
raise AssertionError(f"{out_message}\nActual value: {actual} {log}")
raise AssertionError(
f"{out_message}\nActual value: {actual} {format_call_log(result.get('log'))}"
)


class PageAssertions(AssertionsBase):
Expand Down Expand Up @@ -720,10 +720,7 @@ async def to_be_ok(
if self._is_not:
message = message.replace("expected to", "expected not to")
out_message = self._custom_message or message
log_list = await self._actual._fetch_log()
log = "\n".join(log_list).strip()
if log:
out_message += f"\n Call log:\n{log}"
out_message += format_call_log(await self._actual._fetch_log())

content_type = self._actual.headers.get("content-type")
is_text_encoding = content_type and is_textual_mime_type(content_type)
Expand Down
23 changes: 10 additions & 13 deletions playwright/_impl/_browser.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,17 +27,16 @@
from playwright._impl._artifact import Artifact
from playwright._impl._browser_context import BrowserContext
from playwright._impl._cdp_session import CDPSession
from playwright._impl._connection import ChannelOwner, from_channel
from playwright._impl._connection import ChannelOwner, filter_none, from_channel
from playwright._impl._errors import is_target_closed_error
from playwright._impl._helper import (
BROWSER_CLOSED_ERROR,
ColorScheme,
ForcedColors,
HarContentPolicy,
HarMode,
ReducedMotion,
ServiceWorkersPolicy,
async_readfile,
is_safe_close_error,
locals_to_params,
make_dirs_for_file,
prepare_record_har_options,
Expand All @@ -60,20 +59,19 @@ def __init__(
super().__init__(parent, type, guid, initializer)
self._browser_type = parent
self._is_connected = True
self._is_closed_or_closing = False
self._should_close_connection_on_close = False
self._cr_tracing_path: Optional[str] = None

self._contexts: List[BrowserContext] = []
self._channel.on("close", lambda _: self._on_close())
self._close_reason: Optional[str] = None

def __repr__(self) -> str:
return f"<Browser type={self._browser_type} version={self.version}>"

def _on_close(self) -> None:
self._is_connected = False
self.emit(Browser.Events.Disconnected, self)
self._is_closed_or_closing = True

@property
def contexts(self) -> List[BrowserContext]:
Expand Down Expand Up @@ -179,17 +177,16 @@ async def inner() -> Page:

return await self._connection.wrap_api_call(inner)

async def close(self) -> None:
if self._is_closed_or_closing:
return
self._is_closed_or_closing = True
async def close(self, reason: str = None) -> None:
self._close_reason = reason
try:
await self._channel.send("close")
if self._should_close_connection_on_close:
await self._connection.stop_async()
else:
await self._channel.send("close", filter_none({"reason": reason}))
except Exception as e:
if not is_safe_close_error(e):
if not is_target_closed_error(e):
raise e
if self._should_close_connection_on_close:
await self._connection.stop_async(BROWSER_CLOSED_ERROR)

@property
def version(self) -> str:
Expand Down
30 changes: 20 additions & 10 deletions playwright/_impl/_browser_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,16 +36,17 @@
SetCookieParam,
StorageState,
)
from playwright._impl._api_types import Error
from playwright._impl._artifact import Artifact
from playwright._impl._cdp_session import CDPSession
from playwright._impl._connection import (
ChannelOwner,
filter_none,
from_channel,
from_nullable_channel,
)
from playwright._impl._console_message import ConsoleMessage
from playwright._impl._dialog import Dialog
from playwright._impl._errors import Error, TargetClosedError
from playwright._impl._event_context_manager import EventContextManagerImpl
from playwright._impl._fetch import APIRequestContext
from playwright._impl._frame import Frame
Expand All @@ -70,7 +71,7 @@
from playwright._impl._network import Request, Response, Route, serialize_headers
from playwright._impl._page import BindingCall, Page, Worker
from playwright._impl._tracing import Tracing
from playwright._impl._wait_helper import WaitHelper
from playwright._impl._waiter import Waiter
from playwright._impl._web_error import WebError

if TYPE_CHECKING: # pragma: no cover
Expand Down Expand Up @@ -194,6 +195,7 @@ def __init__(
self.once(
self.Events.Close, lambda context: self._closed_future.set_result(True)
)
self._close_reason: Optional[str] = None
self._set_event_to_subscription_mapping(
{
BrowserContext.Events.Console: "console",
Expand Down Expand Up @@ -433,26 +435,27 @@ def expect_event(
) -> EventContextManagerImpl:
if timeout is None:
timeout = self._timeout_settings.timeout()
wait_helper = WaitHelper(self, f"browser_context.expect_event({event})")
wait_helper.reject_on_timeout(
waiter = Waiter(self, f"browser_context.expect_event({event})")
waiter.reject_on_timeout(
timeout, f'Timeout {timeout}ms exceeded while waiting for event "{event}"'
)
if event != BrowserContext.Events.Close:
wait_helper.reject_on_event(
self, BrowserContext.Events.Close, Error("Context closed")
waiter.reject_on_event(
self, BrowserContext.Events.Close, lambda: TargetClosedError()
)
wait_helper.wait_for_event(self, event, predicate)
return EventContextManagerImpl(wait_helper.result())
waiter.wait_for_event(self, event, predicate)
return EventContextManagerImpl(waiter.result())

def _on_close(self) -> None:
if self._browser:
self._browser._contexts.remove(self)

self.emit(BrowserContext.Events.Close, self)

async def close(self) -> None:
async def close(self, reason: str = None) -> None:
if self._close_was_called:
return
self._close_reason = reason
self._close_was_called = True

async def _inner_close() -> None:
Expand All @@ -479,7 +482,7 @@ async def _inner_close() -> None:
await har.delete()

await self._channel._connection.wrap_api_call(_inner_close, True)
await self._channel.send("close")
await self._channel.send("close", filter_none({"reason": reason}))
await self._closed_future

async def storage_state(self, path: Union[str, Path] = None) -> StorageState:
Expand All @@ -488,6 +491,13 @@ async def storage_state(self, path: Union[str, Path] = None) -> StorageState:
await async_writefile(path, json.dumps(result))
return result

def _effective_close_reason(self) -> Optional[str]:
if self._close_reason:
return self._close_reason
if self._browser:
return self._browser._close_reason
return None

async def wait_for_event(
self, event: str, predicate: Callable = None, timeout: float = None
) -> Any:
Expand Down
7 changes: 3 additions & 4 deletions playwright/_impl/_browser_type.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@
ProxySettings,
ViewportSize,
)
from playwright._impl._api_types import Error
from playwright._impl._browser import Browser, prepare_browser_context_params
from playwright._impl._browser_context import BrowserContext
from playwright._impl._connection import (
Expand All @@ -33,8 +32,8 @@
from_channel,
from_nullable_channel,
)
from playwright._impl._errors import Error
from playwright._impl._helper import (
BROWSER_CLOSED_ERROR,
ColorScheme,
Env,
ForcedColors,
Expand All @@ -46,7 +45,7 @@
)
from playwright._impl._json_pipe import JsonPipeTransport
from playwright._impl._network import serialize_headers
from playwright._impl._wait_helper import throw_on_timeout
from playwright._impl._waiter import throw_on_timeout

if TYPE_CHECKING:
from playwright._impl._playwright import Playwright
Expand Down Expand Up @@ -249,7 +248,7 @@ def handle_transport_close() -> None:
page._on_close()
context._on_close()
browser._on_close()
connection.cleanup(BROWSER_CLOSED_ERROR)
connection.cleanup()

transport.once("close", handle_transport_close)

Expand Down
34 changes: 22 additions & 12 deletions playwright/_impl/_connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
from pyee.asyncio import AsyncIOEventEmitter

import playwright
from playwright._impl._errors import TargetClosedError
from playwright._impl._helper import Error, ParsedMessagePayload, parse_error
from playwright._impl._transport import Transport

Expand Down Expand Up @@ -250,7 +251,7 @@ def __init__(
] = contextvars.ContextVar("ApiZone", default=None)
self._local_utils: Optional["LocalUtils"] = local_utils
self._tracing_count = 0
self._closed_error_message: Optional[str] = None
self._closed_error: Optional[Exception] = None

@property
def local_utils(self) -> "LocalUtils":
Expand Down Expand Up @@ -281,21 +282,21 @@ def stop_sync(self) -> None:
self._loop.run_until_complete(self._transport.wait_until_stopped())
self.cleanup()

async def stop_async(self, error_message: str = None) -> None:
async def stop_async(self) -> None:
self._transport.request_stop()
await self._transport.wait_until_stopped()
self.cleanup(error_message)
self.cleanup()

def cleanup(self, error_message: str = None) -> None:
if not error_message:
error_message = "Connection closed"
self._closed_error_message = error_message
def cleanup(self, cause: Exception = None) -> None:
self._closed_error = (
TargetClosedError(str(cause)) if cause else TargetClosedError()
)
if self._init_task and not self._init_task.done():
self._init_task.cancel()
for ws_connection in self._child_ws_connections:
ws_connection._transport.dispose()
for callback in self._callbacks.values():
callback.future.set_exception(Error(error_message))
callback.future.set_exception(self._closed_error)
self._callbacks.clear()
self.emit("close")

Expand All @@ -313,8 +314,8 @@ def set_in_tracing(self, is_tracing: bool) -> None:
def _send_message_to_server(
self, object: ChannelOwner, method: str, params: Dict, no_reply: bool = False
) -> ProtocolCallback:
if self._closed_error_message:
raise Error(self._closed_error_message)
if self._closed_error:
raise self._closed_error
if object._was_collected:
raise Error(
"The object has been collected to prevent unbounded heap growth."
Expand Down Expand Up @@ -361,7 +362,7 @@ def _send_message_to_server(
return callback

def dispatch(self, msg: ParsedMessagePayload) -> None:
if self._closed_error_message:
if self._closed_error:
return
id = msg.get("id")
if id:
Expand All @@ -373,11 +374,12 @@ def dispatch(self, msg: ParsedMessagePayload) -> None:
if callback.no_reply:
return
error = msg.get("error")
if error:
if error and not msg.get("result"):
parsed_error = parse_error(error["error"]) # type: ignore
parsed_error._stack = "".join(
traceback.format_list(callback.stack_trace)[-10:]
)
parsed_error._message += format_call_log(msg.get("log")) # type: ignore
callback.future.set_exception(parsed_error)
else:
result = self._replace_guids_with_channels(msg.get("result"))
Expand Down Expand Up @@ -565,3 +567,11 @@ def _extract_stack_trace_information_from_stack(

def filter_none(d: Mapping) -> Dict:
return {k: v for k, v in d.items() if v is not None}


def format_call_log(log: Optional[List[str]]) -> str:
if not log:
return ""
if len(list(filter(lambda x: x.strip(), log))) == 0:
return ""
return "\nCall log:\n" + "\n - ".join(log) + "\n"
2 changes: 1 addition & 1 deletion playwright/_impl/_download.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ async def delete(self) -> None:
async def failure(self) -> Optional[str]:
return await self._artifact.failure()

async def path(self) -> Optional[pathlib.Path]:
async def path(self) -> pathlib.Path:
return await self._artifact.path_after_finished()

async def save_as(self, path: Union[str, Path]) -> None:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@
from typing import Optional


def is_target_closed_error(error: Exception) -> bool:
return isinstance(error, TargetClosedError)


class Error(Exception):
def __init__(self, message: str) -> None:
self._message = message
Expand All @@ -41,3 +45,8 @@ def stack(self) -> Optional[str]:

class TimeoutError(Error):
pass


class TargetClosedError(Error):
def __init__(self, message: str = None) -> None:
super().__init__(message or "Target page, context or browser has been closed")
Loading
Loading