Skip to content

Commit

Permalink
chore(roll): roll Playwright to 1.40.0-alpha-oct-18-2023
Browse files Browse the repository at this point in the history
  • Loading branch information
mxschmitt committed Nov 16, 2023
1 parent fc91dfd commit 5dd97ce
Show file tree
Hide file tree
Showing 42 changed files with 256 additions and 197 deletions.
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

0 comments on commit 5dd97ce

Please sign in to comment.