Skip to content

Commit 359f8cc

Browse files
authored
feat(cdp): expose cdp session (microsoft#133)
1 parent 87dceff commit 359f8cc

14 files changed

+471
-92
lines changed

playwright/async_api.py

Lines changed: 110 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,10 @@
2828
from playwright.browser_context import BrowserContext as BrowserContextImpl
2929
from playwright.browser_server import BrowserServer as BrowserServerImpl
3030
from playwright.browser_type import BrowserType as BrowserTypeImpl
31+
from playwright.cdp_session import CDPSession as CDPSessionImpl
32+
from playwright.chromium_browser_context import (
33+
ChromiumBrowserContext as ChromiumBrowserContextImpl,
34+
)
3135
from playwright.console_message import ConsoleMessage as ConsoleMessageImpl
3236
from playwright.dialog import Dialog as DialogImpl
3337
from playwright.download import Download as DownloadImpl
@@ -647,7 +651,7 @@ def __init__(self, obj: JSHandleImpl):
647651
super().__init__(obj)
648652

649653
async def evaluate(
650-
self, expression: str, arg: typing.Any = None, force_expr: bool = False
654+
self, expression: str, arg: typing.Any = None, force_expr: bool = None
651655
) -> typing.Any:
652656
"""JSHandle.evaluate
653657
@@ -676,7 +680,7 @@ async def evaluate(
676680
)
677681

678682
async def evaluateHandle(
679-
self, expression: str, arg: typing.Any = None, force_expr: bool = False
683+
self, expression: str, arg: typing.Any = None, force_expr: bool = None
680684
) -> "JSHandle":
681685
"""JSHandle.evaluateHandle
682686
@@ -1034,7 +1038,7 @@ async def selectOption(
10341038
typing.List[str],
10351039
typing.List["ElementHandle"],
10361040
typing.List[SelectOption],
1037-
],
1041+
] = None,
10381042
timeout: int = None,
10391043
noWaitAfter: bool = None,
10401044
) -> typing.List[str]:
@@ -1336,7 +1340,7 @@ async def evalOnSelector(
13361340
selector: str,
13371341
expression: str,
13381342
arg: typing.Any = None,
1339-
force_expr: bool = False,
1343+
force_expr: bool = None,
13401344
) -> typing.Any:
13411345
"""ElementHandle.evalOnSelector
13421346
@@ -1374,7 +1378,7 @@ async def evalOnSelectorAll(
13741378
selector: str,
13751379
expression: str,
13761380
arg: typing.Any = None,
1377-
force_expr: bool = False,
1381+
force_expr: bool = None,
13781382
) -> typing.Any:
13791383
"""ElementHandle.evalOnSelectorAll
13801384
@@ -1699,7 +1703,7 @@ async def frameElement(self) -> "ElementHandle":
16991703
return mapping.from_impl(await self._impl_obj.frameElement())
17001704

17011705
async def evaluate(
1702-
self, expression: str, arg: typing.Any = None, force_expr: bool = False
1706+
self, expression: str, arg: typing.Any = None, force_expr: bool = None
17031707
) -> typing.Any:
17041708
"""Frame.evaluate
17051709
@@ -1729,7 +1733,7 @@ async def evaluate(
17291733
)
17301734

17311735
async def evaluateHandle(
1732-
self, expression: str, arg: typing.Any = None, force_expr: bool = False
1736+
self, expression: str, arg: typing.Any = None, force_expr: bool = None
17331737
) -> "JSHandle":
17341738
"""Frame.evaluateHandle
17351739
@@ -1878,7 +1882,7 @@ async def evalOnSelector(
18781882
selector: str,
18791883
expression: str,
18801884
arg: typing.Any = None,
1881-
force_expr: bool = False,
1885+
force_expr: bool = None,
18821886
) -> typing.Any:
18831887
"""Frame.evalOnSelector
18841888
@@ -1916,7 +1920,7 @@ async def evalOnSelectorAll(
19161920
selector: str,
19171921
expression: str,
19181922
arg: typing.Any = None,
1919-
force_expr: bool = False,
1923+
force_expr: bool = None,
19201924
) -> typing.Any:
19211925
"""Frame.evalOnSelectorAll
19221926
@@ -2334,7 +2338,7 @@ async def selectOption(
23342338
typing.List[str],
23352339
typing.List["ElementHandle"],
23362340
typing.List[SelectOption],
2337-
],
2341+
] = None,
23382342
timeout: int = None,
23392343
noWaitAfter: bool = None,
23402344
) -> typing.List[str]:
@@ -2556,7 +2560,7 @@ async def waitForFunction(
25562560
self,
25572561
expression: str,
25582562
arg: typing.Any = None,
2559-
force_expr: bool = False,
2563+
force_expr: bool = None,
25602564
timeout: int = None,
25612565
polling: typing.Union[int, Literal["raf"]] = None,
25622566
) -> "JSHandle":
@@ -2640,7 +2644,7 @@ def url(self) -> str:
26402644
return mapping.from_maybe_impl(self._impl_obj.url)
26412645

26422646
async def evaluate(
2643-
self, expression: str, arg: typing.Any = None, force_expr: bool = False
2647+
self, expression: str, arg: typing.Any = None, force_expr: bool = None
26442648
) -> typing.Any:
26452649
"""Worker.evaluate
26462650
@@ -2668,7 +2672,7 @@ async def evaluate(
26682672
)
26692673

26702674
async def evaluateHandle(
2671-
self, expression: str, arg: typing.Any = None, force_expr: bool = False
2675+
self, expression: str, arg: typing.Any = None, force_expr: bool = None
26722676
) -> "JSHandle":
26732677
"""Worker.evaluateHandle
26742678
@@ -2704,7 +2708,11 @@ def __init__(self, obj: SelectorsImpl):
27042708
super().__init__(obj)
27052709

27062710
async def register(
2707-
self, name: str, source: str = "", path: str = None, contentScript: bool = False
2711+
self,
2712+
name: str,
2713+
source: str = None,
2714+
path: str = None,
2715+
contentScript: bool = None,
27082716
) -> NoneType:
27092717
"""Selectors.register
27102718
@@ -2714,9 +2722,9 @@ async def register(
27142722
----------
27152723
name : str
27162724
Name that is used in selectors as a prefix, e.g. `{name: 'foo'}` enables `foo=myselectorbody` selectors. May only contain `[a-zA-Z0-9_]` characters.
2717-
source : str
2725+
source : Optional[str]
27182726
Script that evaluates to a selector engine instance.
2719-
contentScript : bool
2727+
contentScript : Optional[bool]
27202728
Whether to run this selector engine in isolated JavaScript environment. This environment has access to the same DOM, but not any JavaScript objects from the frame's scripts. Defaults to `false`. Note that running as a content script is not guaranteed when this engine is used together with other registered engines.
27212729
"""
27222730
return mapping.from_maybe_impl(
@@ -3198,7 +3206,7 @@ async def dispatchEvent(
31983206
)
31993207

32003208
async def evaluate(
3201-
self, expression: str, arg: typing.Any = None, force_expr: bool = False
3209+
self, expression: str, arg: typing.Any = None, force_expr: bool = None
32023210
) -> typing.Any:
32033211
"""Page.evaluate
32043212
@@ -3230,7 +3238,7 @@ async def evaluate(
32303238
)
32313239

32323240
async def evaluateHandle(
3233-
self, expression: str, arg: typing.Any = None, force_expr: bool = False
3241+
self, expression: str, arg: typing.Any = None, force_expr: bool = None
32343242
) -> "JSHandle":
32353243
"""Page.evaluateHandle
32363244
@@ -3264,7 +3272,7 @@ async def evalOnSelector(
32643272
selector: str,
32653273
expression: str,
32663274
arg: typing.Any = None,
3267-
force_expr: bool = False,
3275+
force_expr: bool = None,
32683276
) -> typing.Any:
32693277
"""Page.evalOnSelector
32703278
@@ -3303,7 +3311,7 @@ async def evalOnSelectorAll(
33033311
selector: str,
33043312
expression: str,
33053313
arg: typing.Any = None,
3306-
force_expr: bool = False,
3314+
force_expr: bool = None,
33073315
) -> typing.Any:
33083316
"""Page.evalOnSelectorAll
33093317
@@ -4281,7 +4289,7 @@ async def selectOption(
42814289
typing.List[str],
42824290
typing.List["ElementHandle"],
42834291
typing.List[SelectOption],
4284-
],
4292+
] = None,
42854293
timeout: int = None,
42864294
noWaitAfter: bool = None,
42874295
) -> typing.List[str]:
@@ -4505,7 +4513,7 @@ async def waitForFunction(
45054513
self,
45064514
expression: str,
45074515
arg: typing.Any = None,
4508-
force_expr: bool = False,
4516+
force_expr: bool = None,
45094517
timeout: int = None,
45104518
polling: typing.Union[int, Literal["raf"]] = None,
45114519
) -> "JSHandle":
@@ -4913,7 +4921,7 @@ async def clearPermissions(self) -> NoneType:
49134921
"""
49144922
return mapping.from_maybe_impl(await self._impl_obj.clearPermissions())
49154923

4916-
async def setGeolocation(self, geolocation: typing.Dict) -> NoneType:
4924+
async def setGeolocation(self, geolocation: typing.Dict = None) -> NoneType:
49174925
"""BrowserContext.setGeolocation
49184926
49194927
Sets the context's geolocation. Passing `null` or `undefined` emulates position unavailable.
@@ -5133,6 +5141,85 @@ def expect_page(
51335141
mapping.register(BrowserContextImpl, BrowserContext)
51345142

51355143

5144+
class CDPSession(AsyncBase):
5145+
def __init__(self, obj: CDPSessionImpl):
5146+
super().__init__(obj)
5147+
5148+
async def send(self, method: str, params: typing.Dict = None) -> typing.Dict:
5149+
"""CDPSession.send
5150+
5151+
Parameters
5152+
----------
5153+
method : str
5154+
protocol method name
5155+
params : Optional[typing.Dict]
5156+
Optional method parameters
5157+
5158+
Returns
5159+
-------
5160+
typing.Dict
5161+
"""
5162+
return mapping.from_maybe_impl(
5163+
await self._impl_obj.send(method=method, params=params)
5164+
)
5165+
5166+
async def detach(self) -> NoneType:
5167+
"""CDPSession.detach
5168+
5169+
Detaches the CDPSession from the target. Once detached, the CDPSession object won't emit any events and can't be used
5170+
to send messages.
5171+
"""
5172+
return mapping.from_maybe_impl(await self._impl_obj.detach())
5173+
5174+
5175+
mapping.register(CDPSessionImpl, CDPSession)
5176+
5177+
5178+
class ChromiumBrowserContext(BrowserContext):
5179+
def __init__(self, obj: ChromiumBrowserContextImpl):
5180+
super().__init__(obj)
5181+
5182+
def backgroundPages(self) -> typing.List["Page"]:
5183+
"""ChromiumBrowserContext.backgroundPages
5184+
5185+
Returns
5186+
-------
5187+
typing.List[Page]
5188+
All existing background pages in the context.
5189+
"""
5190+
return mapping.from_impl_list(self._impl_obj.backgroundPages())
5191+
5192+
def serviceWorkers(self) -> typing.List["Worker"]:
5193+
"""ChromiumBrowserContext.serviceWorkers
5194+
5195+
Returns
5196+
-------
5197+
typing.List[Worker]
5198+
All existing service workers in the context.
5199+
"""
5200+
return mapping.from_impl_list(self._impl_obj.serviceWorkers())
5201+
5202+
async def newCDPSession(self, page: "Page") -> "CDPSession":
5203+
"""ChromiumBrowserContext.newCDPSession
5204+
5205+
Parameters
5206+
----------
5207+
page : Page
5208+
Page to create new session for.
5209+
5210+
Returns
5211+
-------
5212+
CDPSession
5213+
Promise that resolves to the newly created session.
5214+
"""
5215+
return mapping.from_impl(
5216+
await self._impl_obj.newCDPSession(page=page._impl_obj)
5217+
)
5218+
5219+
5220+
mapping.register(ChromiumBrowserContextImpl, ChromiumBrowserContext)
5221+
5222+
51365223
class Browser(AsyncBase):
51375224
def __init__(self, obj: BrowserImpl):
51385225
super().__init__(obj)

playwright/browser.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414

1515
import sys
1616
from types import SimpleNamespace
17-
from typing import Dict, List, Union
17+
from typing import TYPE_CHECKING, Dict, List, Union
1818

1919
from playwright.browser_context import BrowserContext
2020
from playwright.connection import ChannelOwner, from_channel
@@ -27,15 +27,19 @@
2727
else: # pragma: no cover
2828
from typing_extensions import Literal
2929

30+
if TYPE_CHECKING: # pragma: no cover
31+
from playwright.browser_type import BrowserType
32+
3033

3134
class Browser(ChannelOwner):
3235

3336
Events = SimpleNamespace(Disconnected="disconnected",)
3437

3538
def __init__(
36-
self, parent: ChannelOwner, type: str, guid: str, initializer: Dict
39+
self, parent: "BrowserType", type: str, guid: str, initializer: Dict
3740
) -> None:
3841
super().__init__(parent, type, guid, initializer)
42+
self._browser_type = parent
3943
self._is_connected = True
4044
self._is_closed_or_closing = False
4145

playwright/cdp_session.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
# Copyright (c) Microsoft Corporation.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
from typing import Any, Dict
16+
17+
from playwright.connection import ChannelOwner
18+
from playwright.js_handle import parse_result, serialize_argument
19+
20+
21+
class CDPSession(ChannelOwner):
22+
def __init__(
23+
self, parent: ChannelOwner, type: str, guid: str, initializer: Dict
24+
) -> None:
25+
super().__init__(parent, type, guid, initializer)
26+
self._channel.on("event", lambda params: self._on_event(params))
27+
28+
def _on_event(self, params: Any) -> None:
29+
self.emit(params["method"], parse_result(params["params"]))
30+
31+
async def send(self, method: str, params: Dict = None) -> Dict:
32+
payload = {"method": method}
33+
if params:
34+
payload["params"] = serialize_argument(params)["value"]
35+
result = await self._channel.send("send", payload)
36+
return parse_result(result)
37+
38+
async def detach(self) -> None:
39+
await self._channel.send("detach")

0 commit comments

Comments
 (0)