Skip to content
Draft
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
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 -->145.0.7632.6<!-- GEN:stop --> | ✅ | ✅ | ✅ |
| WebKit <!-- GEN:webkit-version -->26.0<!-- GEN:stop --> | ✅ | ✅ | ✅ |
| Firefox <!-- GEN:firefox-version -->146.0.1<!-- GEN:stop --> | ✅ | ✅ | ✅ |
| Chromium <!-- GEN:chromium-version -->147.0.7727.15<!-- GEN:stop --> | ✅ | ✅ | ✅ |
| WebKit <!-- GEN:webkit-version -->26.4<!-- GEN:stop --> | ✅ | ✅ | ✅ |
| Firefox <!-- GEN:firefox-version -->148.0.2<!-- GEN:stop --> | ✅ | ✅ | ✅ |

## Documentation

Expand Down
23 changes: 21 additions & 2 deletions playwright/_impl/_api_structures.py
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,7 @@ class FrameExpectOptions(TypedDict, total=False):
timeout: Optional[float]


class FrameExpectResult(TypedDict):
class FrameExpectResult(TypedDict, total=False):
matches: bool
received: Any
log: List[str]
Expand Down Expand Up @@ -307,7 +307,26 @@ class FrameExpectResult(TypedDict):
]


class TracingGroupLocation(TypedDict):
class TracingGroupLocation(TypedDict, total=False):
file: str
line: Optional[int]
column: Optional[int]


class BindResult(TypedDict):
endpoint: str


class PausedDetailsLocation(TypedDict, total=False):
file: str
line: Optional[int]
column: Optional[int]


class PausedDetails(TypedDict):
location: PausedDetailsLocation
title: str


class OnFrame(TypedDict):
data: bytes
14 changes: 14 additions & 0 deletions playwright/_impl/_browser.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
)

from playwright._impl._api_structures import (
BindResult,
ClientCertificate,
Geolocation,
HttpCredentials,
Expand Down Expand Up @@ -240,6 +241,19 @@ async def close(self, reason: str = None) -> None:
if not is_target_closed_error(e):
raise e

async def bind(
self,
title: str,
workspaceDir: str = None,
host: str = None,
port: int = None,
) -> BindResult:
params = locals_to_params(locals())
return await self._channel.send("startServer", None, params)

async def unbind(self) -> None:
await self._channel.send("stopServer", None)

@property
def version(self) -> str:
return self._initializer["version"]
Expand Down
41 changes: 33 additions & 8 deletions playwright/_impl/_browser_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,9 @@
from_nullable_channel,
)
from playwright._impl._console_message import ConsoleMessage
from playwright._impl._debugger import Debugger
from playwright._impl._dialog import Dialog
from playwright._impl._disposable import Disposable, DisposableStub
from playwright._impl._errors import Error, TargetClosedError
from playwright._impl._event_context_manager import EventContextManagerImpl
from playwright._impl._fetch import APIRequestContext
Expand Down Expand Up @@ -122,6 +124,7 @@ def __init__(
self._base_url: Optional[str] = self._options.get("baseURL")
self._videos_dir: Optional[str] = self._options.get("recordVideo")
self._tracing = cast(Tracing, from_channel(initializer["tracing"]))
self._debugger: Debugger = from_channel(initializer["debugger"])
self._har_recorders: Dict[str, HarRecordingMetadata] = {}
self._request: APIRequestContext = from_channel(initializer["requestContext"])
self._request._timeout_settings = self._timeout_settings
Expand Down Expand Up @@ -392,16 +395,18 @@ async def set_offline(self, offline: bool) -> None:

async def add_init_script(
self, script: str = None, path: Union[str, Path] = None
) -> None:
) -> Disposable:
if path:
script = (await async_readfile(path)).decode()
if not isinstance(script, str):
raise Error("Either path or script parameter must be specified")
await self._channel.send("addInitScript", None, dict(source=script))
return from_channel(
await self._channel.send("addInitScript", None, dict(source=script))
)

async def expose_binding(
self, name: str, callback: Callable, handle: bool = None
) -> None:
) -> Disposable:
for page in self._pages:
if name in page._bindings:
raise Error(
Expand All @@ -410,16 +415,18 @@ async def expose_binding(
if name in self._bindings:
raise Error(f'Function "{name}" has been already registered')
self._bindings[name] = callback
await self._channel.send(
"exposeBinding", None, dict(name=name, needsHandle=handle or False)
return from_channel(
await self._channel.send(
"exposeBinding", None, dict(name=name, needsHandle=handle or False)
)
)

async def expose_function(self, name: str, callback: Callable) -> None:
await self.expose_binding(name, lambda source, *args: callback(*args))
async def expose_function(self, name: str, callback: Callable) -> Disposable:
return await self.expose_binding(name, lambda source, *args: callback(*args))

async def route(
self, url: URLMatch, handler: RouteHandlerCallback, times: int = None
) -> None:
) -> DisposableStub:
self._routes.insert(
0,
RouteHandler(
Expand All @@ -431,6 +438,7 @@ async def route(
),
)
await self._update_interception_patterns()
return DisposableStub(lambda: self.unroute(url, handler))

async def unroute(
self, url: URLMatch, handler: Optional[RouteHandlerCallback] = None
Expand Down Expand Up @@ -564,6 +572,9 @@ def expect_event(
waiter.wait_for_event(self, event, predicate)
return EventContextManagerImpl(waiter.result())

def is_closed(self) -> bool:
return self._closing_or_closed

def _on_close(self) -> None:
self._closing_or_closed = True
if self._browser:
Expand Down Expand Up @@ -627,6 +638,16 @@ async def storage_state(
await async_writefile(path, json.dumps(result))
return result

async def set_storage_state(
self, storageState: Union[StorageState, str, Path]
) -> None:
state: StorageState
if isinstance(storageState, (str, Path)):
state = json.loads(await async_readfile(storageState))
elif storageState:
state = storageState
await self._channel.send("setStorageState", None, {"storageState": state})

def _effective_close_reason(self) -> Optional[str]:
if self._close_reason:
return self._close_reason
Expand Down Expand Up @@ -753,6 +774,10 @@ async def new_cdp_session(self, page: Union[Page, Frame]) -> CDPSession:
def tracing(self) -> Tracing:
return self._tracing

@property
def debugger(self) -> Debugger:
return self._debugger

@property
def request(self) -> "APIRequestContext":
return self._request
Expand Down
6 changes: 4 additions & 2 deletions playwright/_impl/_browser_type.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ async def launch(
tracesDir: Union[pathlib.Path, str] = None,
chromiumSandbox: bool = None,
firefoxUserPrefs: Dict[str, Union[str, float, bool]] = None,
artifactsDir: Union[Path, str] = None,
) -> Browser:
params = locals_to_params(locals())
normalize_launch_params(params)
Expand Down Expand Up @@ -156,6 +157,7 @@ async def launch_persistent_context(
recordHarMode: HarMode = None,
recordHarContent: HarContentPolicy = None,
clientCertificates: List[ClientCertificate] = None,
artifactsDir: Union[Path, str] = None,
) -> BrowserContext:
userDataDir = self._user_data_dir(userDataDir)
params = locals_to_params(locals())
Expand Down Expand Up @@ -213,7 +215,7 @@ async def connect_over_cdp(

async def connect(
self,
wsEndpoint: str,
endpoint: str,
timeout: float = None,
slowMo: float = None,
headers: Dict[str, str] = None,
Expand All @@ -229,7 +231,7 @@ async def connect(
"connect",
None,
{
"wsEndpoint": wsEndpoint,
"endpoint": endpoint,
"headers": headers,
"slowMo": slowMo,
"timeout": timeout if timeout is not None else 0,
Expand Down
1 change: 1 addition & 0 deletions playwright/_impl/_cdp_session.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ def __init__(
) -> None:
super().__init__(parent, type, guid, initializer)
self._channel.on("event", lambda params: self._on_event(params))
self._channel.on("close", lambda _: self.emit("close"))

def _on_event(self, params: Any) -> None:
self.emit(params["method"], params.get("params"))
Expand Down
4 changes: 4 additions & 0 deletions playwright/_impl/_console_message.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,10 @@ def args(self) -> List[JSHandle]:
def location(self) -> SourceLocation:
return self._event["location"]

@property
def timestamp(self) -> float:
return self._event["timestamp"]

@property
def page(self) -> Optional["Page"]:
return self._page
Expand Down
52 changes: 52 additions & 0 deletions playwright/_impl/_debugger.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# Copyright (c) Microsoft Corporation.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from typing import Dict, Optional

from playwright._impl._api_structures import PausedDetails, PausedDetailsLocation
from playwright._impl._connection import ChannelOwner


class Debugger(ChannelOwner):
Events = {"PausedStateChanged": "pausedStateChanged"}

def __init__(
self, parent: ChannelOwner, type: str, guid: str, initializer: Dict
) -> None:
super().__init__(parent, type, guid, initializer)
self._paused_details: Optional[PausedDetails] = None
self._channel.on(
"pausedStateChanged",
lambda params: self._on_paused_state_changed(params.get("pausedDetails")),
)

def _on_paused_state_changed(self, paused_details: Optional[PausedDetails]) -> None:
self._paused_details = paused_details
self.emit(Debugger.Events["PausedStateChanged"])

async def request_pause(self) -> None:
await self._channel.send("requestPause", None)

async def resume(self) -> None:
await self._channel.send("resume", None)

async def next(self) -> None:
await self._channel.send("next", None)

async def run_to(self, location: PausedDetailsLocation) -> None:
await self._channel.send("runTo", None, {"location": location})

@property
def paused_details(self) -> Optional[PausedDetails]:
return self._paused_details
14 changes: 10 additions & 4 deletions playwright/_impl/_dialog.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
from typing import TYPE_CHECKING, Dict, Optional

from playwright._impl._connection import ChannelOwner, from_nullable_channel
from playwright._impl._errors import is_target_closed_error
from playwright._impl._helper import locals_to_params

if TYPE_CHECKING: # pragma: no cover
Expand Down Expand Up @@ -51,7 +52,12 @@ async def accept(self, promptText: str = None) -> None:
await self._channel.send("accept", None, locals_to_params(locals()))

async def dismiss(self) -> None:
await self._channel.send(
"dismiss",
None,
)
try:
await self._channel.send(
"dismiss",
None,
)
except Exception as e:
if is_target_closed_error(e):
return
raise
55 changes: 55 additions & 0 deletions playwright/_impl/_disposable.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# Copyright (c) Microsoft Corporation.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from typing import Awaitable, Callable, Dict

from playwright._impl._connection import ChannelOwner
from playwright._impl._errors import is_target_closed_error


class Disposable(ChannelOwner):
def __init__(
self, parent: ChannelOwner, type: str, guid: str, initializer: Dict
) -> None:
super().__init__(parent, type, guid, initializer)

async def dispose(self) -> None:
try:
await self._channel.send(
"dispose",
None,
)
except Exception as e:
if not is_target_closed_error(e):
raise e

async def close(self) -> None:
await self.dispose()

def __repr__(self) -> str:
return "<Disposable>"


class DisposableStub:
def __init__(self, dispose_fn: Callable[[], Awaitable[None]]) -> None:
self._dispose_fn = dispose_fn

async def dispose(self) -> None:
await self._dispose_fn()

async def close(self) -> None:
await self.dispose()

def __repr__(self) -> str:
return "<Disposable>"
4 changes: 3 additions & 1 deletion playwright/_impl/_local_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,9 @@ async def har_unzip(self, zipFile: str, harFile: str) -> None:
params = locals_to_params(locals())
await self._channel.send("harUnzip", None, params)

async def tracing_started(self, tracesDir: Optional[str], traceName: str) -> str:
async def tracing_started(
self, tracesDir: Optional[str], traceName: str, live: bool
) -> str:
params = locals_to_params(locals())
return await self._channel.send("tracingStarted", None, params)

Expand Down
Loading
Loading