Skip to content

Commit 493ccff

Browse files
committed
fix(sync): wrap route callback args in sync class
1 parent 03babe9 commit 493ccff

File tree

6 files changed

+66
-36
lines changed

6 files changed

+66
-36
lines changed

README.md

Lines changed: 18 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -48,18 +48,15 @@ Playwright is built to automate the broad and growing set of web browser capabil
4848
This code snippet navigates to whatsmyuseragent.org in Chromium, Firefox and WebKit, and saves 3 screenshots.
4949

5050
```py
51-
import asyncio
5251
from playwright import chromium, firefox, webkit
5352

54-
async def run():
55-
for browser_type in [chromium, firefox, webkit]:
56-
browser = await browser_type.launch()
57-
page = await browser.newPage()
58-
await page.goto('http://whatsmyuseragent.org/')
59-
await page.screenshot(path=f'example-{browser_type.name}.png')
60-
await browser.close()
6153

62-
asyncio.get_event_loop().run_until_complete(run())
54+
for browser_type in [chromium, firefox, webkit]:
55+
browser = browser_type.launch()
56+
page = browser.newPage()
57+
page.goto("http://whatsmyuseragent.org/")
58+
page.screenshot(path=f"example-{browser_type.name}.png")
59+
browser.close()
6360
```
6461

6562
#### Mobile and geolocation
@@ -96,24 +93,22 @@ asyncio.get_event_loop().run_until_complete(run())
9693
This code snippet navigates to example.com in Firefox, and executes a script in the page context.
9794

9895
```py
99-
import asyncio
10096
from playwright import firefox
10197

102-
async def run():
103-
browser = await firefox.launch()
104-
page = await browser.newPage()
105-
await page.goto('https://www.example.com/')
106-
dimensions = await page.evaluate('''() => {
98+
browser = firefox.launch()
99+
page = browser.newPage()
100+
page.goto("https://www.example.com")
101+
dimensions = page.evaluate(
102+
"""() => {
107103
return {
108-
width: document.documentElement.clientWidth,
109-
height: document.documentElement.clientHeight,
110-
deviceScaleFactor: window.devicePixelRatio
104+
width: document.documentElement.clientWidth,
105+
height: document.documentElement.clientHeight,
106+
deviceScaleFactor: window.devicePixelRatio
111107
}
112-
}''')
113-
print(dimensions)
114-
await browser.close()
115-
116-
asyncio.get_event_loop().run_until_complete(run())
108+
}"""
109+
)
110+
print(dimensions)
111+
browser.close()
117112
```
118113

119114
#### Intercept network requests

playwright/helper.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,21 @@ class FrameNavigatedEvent(TypedDict):
117117
error: Optional[str]
118118

119119

120+
Size = TypedDict("Size", {"width": int, "height": int})
121+
122+
DeviceDescriptor = TypedDict(
123+
"DeviceDescriptor",
124+
{
125+
"userAgent": str,
126+
"viewport": Size,
127+
"deviceScaleFactor": int,
128+
"isMobile": bool,
129+
"hasTouch": bool,
130+
},
131+
)
132+
Devices = Dict[str, DeviceDescriptor]
133+
134+
120135
class URLMatcher:
121136
def __init__(self, match: URLMatch) -> None:
122137
self._callback: Optional[Callable[[str], bool]] = None

playwright/playwright.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
from playwright.browser_type import BrowserType
1818
from playwright.connection import ChannelOwner, ConnectionScope, from_channel
19+
from playwright.helper import Devices
1920
from playwright.selectors import Selectors
2021

2122

@@ -24,7 +25,7 @@ class Playwright(ChannelOwner):
2425
firefox: BrowserType
2526
webkit: BrowserType
2627
selectors: Selectors
27-
devices: Dict
28+
devices: Devices
2829

2930
def __init__(self, scope: ConnectionScope, guid: str, initializer: Dict) -> None:
3031
super().__init__(scope, guid, initializer)

playwright/sync.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
# limitations under the License.
1414

1515
import sys
16+
import types
1617
import typing
1718

1819
from playwright.sync_base import EventContextManager, SyncBase, mapping
@@ -35,6 +36,7 @@
3536
from playwright.frame import Frame as FrameAsync
3637
from playwright.helper import (
3738
ConsoleMessageLocation,
39+
DeviceDescriptor,
3840
Error,
3941
FilePayload,
4042
SelectOption,
@@ -1884,6 +1886,8 @@ def route(
18841886
url: typing.Union[str, typing.Pattern, typing.Callable[[str], bool]],
18851887
handler: typing.Callable[["Route", "Request"], typing.Any],
18861888
) -> NoneType:
1889+
if isinstance(handler, types.FunctionType):
1890+
handler = self._map_event(handler)
18871891
return self._sync(self._async_obj.route(url=url, handler=handler))
18881892

18891893
def unroute(
@@ -2338,6 +2342,8 @@ def route(
23382342
match: typing.Union[str, typing.Pattern, typing.Callable[[str], bool]],
23392343
handler: typing.Callable[["Route", "Request"], typing.Any],
23402344
) -> NoneType:
2345+
if isinstance(handler, types.FunctionType):
2346+
handler = self._map_event(handler)
23412347
return self._sync(self._async_obj.route(match=match, handler=handler))
23422348

23432349
def unroute(
@@ -2801,7 +2807,7 @@ def selectors(self) -> "Selectors":
28012807
return Selectors._from_async(self._async_obj.selectors)
28022808

28032809
@property
2804-
def devices(self) -> typing.Dict:
2810+
def devices(self) -> typing.Dict[str, DeviceDescriptor]:
28052811
return self._async_obj.devices
28062812

28072813

playwright/sync_base.py

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -17,20 +17,20 @@
1717

1818
from playwright.wait_helper import WaitHelper
1919

20-
loop = asyncio.get_event_loop()
21-
2220

2321
class AsyncToSyncMapping:
2422
mapping: List[Tuple[type, type]] = []
2523

2624
def register(self, async_class: type, sync_class: type) -> None:
2725
self.mapping.append((async_class, sync_class))
2826

29-
def get_sync_class(self, input_async_inst: object) -> Any:
27+
def get_sync_value_nullabe(self, input_async_inst: object) -> Any:
28+
if not input_async_inst:
29+
return input_async_inst
3030
for (async_class, sync_class) in self.mapping:
3131
if isinstance(input_async_inst, async_class):
32-
return sync_class
33-
raise ValueError("should never happen")
32+
return sync_class._from_async(input_async_inst) # type: ignore
33+
return input_async_inst
3434

3535

3636
mapping = AsyncToSyncMapping()
@@ -47,20 +47,21 @@ def __init__(
4747
timeout: int = None,
4848
) -> None:
4949
self._value: Optional[T] = None
50+
self._loop = asyncio.get_event_loop()
5051

5152
wait_helper = WaitHelper()
5253
wait_helper.reject_on_timeout(
5354
timeout or 30000, f'Timeout while waiting for event "${event}"'
5455
)
55-
self._future = loop.create_task(
56+
self._future = self._loop.create_task(
5657
wait_helper.wait_for_event(sync_base._async_obj, event, predicate)
5758
)
5859

5960
@property
6061
def value(self) -> T:
6162
if not self._value:
62-
value = loop.run_until_complete(self._future)
63-
self._value = mapping.get_sync_class(value)._from_async(value)
63+
value = self._loop.run_until_complete(self._future)
64+
self._value = mapping.get_sync_value_nullabe(value)
6465
return cast(T, self._value)
6566

6667

@@ -84,15 +85,19 @@ def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
8485
class SyncBase:
8586
def __init__(self, async_obj: Any) -> None:
8687
self._async_obj = async_obj
88+
self._loop = asyncio.get_event_loop()
8789

8890
def __str__(self) -> str:
8991
return self._async_obj.__str__()
9092

9193
def _sync(self, future: asyncio.Future) -> Any:
92-
return loop.run_until_complete(future)
94+
return self._loop.run_until_complete(future)
9395

9496
def _map_event(self, handler: Callable[[Any], None]) -> Callable[[Any], None]:
95-
return lambda event: handler(mapping.get_sync_class(event)._from_async(event))
97+
def _wrap_handler(*args: Any) -> None:
98+
handler(*list(map(mapping.get_sync_value_nullabe, args)))
99+
100+
return _wrap_handler
96101

97102
def on(self, event_name: str, handler: Any) -> None:
98103
self._async_obj.on(event_name, self._map_event(handler))

scripts/generate_sync_api.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,12 @@ def return_value(value: Any) -> List[str]:
136136
return [f"{wrap_type}._from_async(", ")"]
137137

138138

139+
def wrap_input_parameters(class_name: str, method_name: str) -> None:
140+
if class_name in ["Page", "BrowserContext"] and method_name == "route":
141+
print(" if isinstance(handler, types.FunctionType):")
142+
print(" handler = self._map_event(handler)")
143+
144+
139145
def generate(t: Any) -> None:
140146
print("")
141147
print(f"class {short_name(t)}(SyncBase):")
@@ -201,6 +207,7 @@ def generate(t: Any) -> None:
201207
print(
202208
f" def {name}({signature(value, len(name) + 9)}) -> {return_type(value)}:"
203209
)
210+
wrap_input_parameters(short_name(t), name)
204211
[prefix, suffix] = return_value(get_type_hints(value, globals())["return"])
205212
prefix = " return " + prefix + f"self._sync(self._async_obj.{name}("
206213
suffix = "))" + suffix
@@ -241,6 +248,7 @@ def main() -> None:
241248
# See the License for the specific language governing permissions and
242249
# limitations under the License.
243250
251+
import types
244252
import typing
245253
import sys
246254
from playwright.sync_base import EventContextManager, SyncBase, mapping
@@ -261,7 +269,7 @@ def main() -> None:
261269
from playwright.element_handle import ElementHandle as ElementHandleAsync
262270
from playwright.file_chooser import FileChooser as FileChooserAsync
263271
from playwright.frame import Frame as FrameAsync
264-
from playwright.helper import ConsoleMessageLocation, Error, FilePayload, SelectOption, Viewport
272+
from playwright.helper import ConsoleMessageLocation, Error, FilePayload, SelectOption, Viewport, DeviceDescriptor
265273
from playwright.input import Keyboard as KeyboardAsync, Mouse as MouseAsync
266274
from playwright.js_handle import JSHandle as JSHandleAsync
267275
from playwright.network import Request as RequestAsync, Response as ResponseAsync, Route as RouteAsync

0 commit comments

Comments
 (0)