Skip to content

Commit c05cad2

Browse files
authored
chore: roll Playwright to ToT (microsoft#516)
1 parent 7227b87 commit c05cad2

19 files changed

+2053
-5046
lines changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@ Playwright is a Python library to automate [Chromium](https://www.chromium.org/H
66

77
| | Linux | macOS | Windows |
88
| :--- | :---: | :---: | :---: |
9-
| Chromium <!-- GEN:chromium-version -->90.0.4396.0<!-- GEN:stop --> ||||
9+
| Chromium <!-- GEN:chromium-version -->90.0.4412.0<!-- GEN:stop --> ||||
1010
| WebKit <!-- GEN:webkit-version -->14.1<!-- GEN:stop --> ||||
11-
| Firefox <!-- GEN:firefox-version -->85.0b10<!-- GEN:stop --> ||||
11+
| Firefox <!-- GEN:firefox-version -->86.0b9<!-- GEN:stop --> ||||
1212

1313
Headless execution is supported for all browsers on all platforms.
1414

playwright/_impl/_browser.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ async def new_context(
9292
storageState: Union[StorageState, str, Path] = None,
9393
) -> BrowserContext:
9494
params = locals_to_params(locals())
95-
normalize_context_params(params)
95+
normalize_context_params(self._connection._is_sync, params)
9696

9797
channel = await self._channel.send("newContext", params)
9898
context = from_channel(channel)
@@ -151,7 +151,8 @@ def version(self) -> str:
151151
return self._initializer["version"]
152152

153153

154-
def normalize_context_params(params: Dict) -> None:
154+
def normalize_context_params(is_sync: bool, params: Dict) -> None:
155+
params["sdkLanguage"] = "python" if is_sync else "python-async"
155156
if params.get("noViewport"):
156157
del params["noViewport"]
157158
params["noDefaultViewport"] = True

playwright/_impl/_browser_context.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -207,7 +207,7 @@ def expect_event(
207207
) -> EventContextManagerImpl:
208208
if timeout is None:
209209
timeout = self._timeout_settings.timeout()
210-
wait_helper = WaitHelper(self._loop)
210+
wait_helper = WaitHelper(self, f"expect_event({event})")
211211
wait_helper.reject_on_timeout(
212212
timeout, f'Timeout while waiting for event "{event}"'
213213
)

playwright/_impl/_browser_type.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ async def launch_persistent_context(
115115
) -> BrowserContext:
116116
userDataDir = str(Path(userDataDir))
117117
params = locals_to_params(locals())
118-
normalize_context_params(params)
118+
normalize_context_params(self._connection._is_sync, params)
119119
normalize_launch_params(params)
120120
try:
121121
context = from_channel(

playwright/_impl/_connection.py

Lines changed: 57 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
import sys
1717
import traceback
1818
from pathlib import Path
19-
from typing import Any, Callable, Dict, Optional, Union
19+
from typing import Any, Callable, Dict, List, Optional, Union
2020

2121
from greenlet import greenlet
2222
from pyee import AsyncIOEventEmitter
@@ -92,6 +92,32 @@ def __init__(
9292
if self._parent:
9393
self._parent._objects[guid] = self
9494

95+
def _wait_for_event_info_before(self, wait_id: str, name: str) -> None:
96+
self._connection._send_message_to_server(
97+
self._guid,
98+
"waitForEventInfo",
99+
{
100+
"info": {
101+
"name": name,
102+
"waitId": wait_id,
103+
"phase": "before",
104+
"stack": capture_call_stack(),
105+
}
106+
},
107+
)
108+
109+
def _wait_for_event_info_after(
110+
self, wait_id: str, exception: Exception = None
111+
) -> None:
112+
info = {"waitId": wait_id, "phase": "after"}
113+
if exception:
114+
info["error"] = str(exception)
115+
self._connection._send_message_to_server(
116+
self._guid,
117+
"waitForEventInfo",
118+
{"info": info},
119+
)
120+
95121
def _dispose(self) -> None:
96122
# Clean up from parent and connection.
97123
if self._parent:
@@ -106,7 +132,7 @@ def _dispose(self) -> None:
106132

107133
class ProtocolCallback:
108134
def __init__(self, loop: asyncio.AbstractEventLoop) -> None:
109-
self.stack_trace = "".join(traceback.format_stack()[-10:])
135+
self.stack_trace: traceback.StackSummary = traceback.StackSummary()
110136
self.future = loop.create_future()
111137

112138

@@ -166,14 +192,23 @@ def _send_message_to_server(
166192
) -> ProtocolCallback:
167193
self._last_id += 1
168194
id = self._last_id
195+
callback = ProtocolCallback(self._loop)
196+
if self._is_sync:
197+
task = asyncio.current_task(self._loop)
198+
callback.stack_trace = (
199+
getattr(task, "__pw_stack_trace__", None) if task else None
200+
)
201+
if not callback.stack_trace:
202+
callback.stack_trace = traceback.extract_stack()
203+
169204
message = dict(
170205
id=id,
171206
guid=guid,
172207
method=method,
173208
params=self._replace_channels_with_guids(params, "params"),
209+
metadata={"stack": serialize_call_stack(callback.stack_trace)},
174210
)
175211
self._transport.send(message)
176-
callback = ProtocolCallback(self._loop)
177212
self._callbacks[id] = callback
178213
return callback
179214

@@ -184,7 +219,9 @@ def _dispatch(self, msg: ParsedMessagePayload) -> None:
184219
error = msg.get("error")
185220
if error:
186221
parsed_error = parse_error(error["error"]) # type: ignore
187-
parsed_error.stack = callback.stack_trace
222+
parsed_error.stack = "".join(
223+
traceback.format_list(callback.stack_trace)[-10:]
224+
)
188225
callback.future.set_exception(parsed_error)
189226
else:
190227
result = self._replace_guids_with_channels(msg.get("result"))
@@ -267,3 +304,19 @@ def from_channel(channel: Channel) -> Any:
267304

268305
def from_nullable_channel(channel: Optional[Channel]) -> Optional[Any]:
269306
return channel._object if channel else None
307+
308+
309+
def serialize_call_stack(stack_trace: traceback.StackSummary) -> List[Dict]:
310+
stack: List[Dict] = []
311+
for frame in stack_trace:
312+
if "_generated.py" in frame.filename:
313+
break
314+
stack.append(
315+
{"file": frame.filename, "line": frame.lineno, "function": frame.name}
316+
)
317+
stack.reverse()
318+
return stack
319+
320+
321+
def capture_call_stack() -> List[Dict]:
322+
return serialize_call_stack(traceback.extract_stack())

playwright/_impl/_driver.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818
from pathlib import Path
1919

2020
import playwright
21-
from playwright._impl._logger import init_logger
2221

2322

2423
def compute_driver_executable() -> Path:
@@ -38,5 +37,3 @@ def compute_driver_executable() -> Path:
3837
# RuntimeError: Cannot add child handler, the child watcher does not have a loop attached
3938
asyncio.get_event_loop()
4039
asyncio.get_child_watcher()
41-
42-
init_logger()

playwright/_impl/_frame.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -115,8 +115,10 @@ async def goto(
115115
),
116116
)
117117

118-
def _setup_navigation_wait_helper(self, timeout: float = None) -> WaitHelper:
119-
wait_helper = WaitHelper(self._loop)
118+
def _setup_navigation_wait_helper(
119+
self, wait_name: str, timeout: float = None
120+
) -> WaitHelper:
121+
wait_helper = WaitHelper(self, wait_name)
120122
wait_helper.reject_on_event(
121123
self._page, "close", Error("Navigation failed because page was closed!")
122124
)
@@ -146,7 +148,7 @@ def expect_navigation(
146148
if timeout is None:
147149
timeout = self._page._timeout_settings.navigation_timeout()
148150
deadline = monotonic_time() + timeout
149-
wait_helper = self._setup_navigation_wait_helper(timeout)
151+
wait_helper = self._setup_navigation_wait_helper("expect_navigation", timeout)
150152
matcher = URLMatcher(url) if url else None
151153

152154
def predicate(event: Any) -> bool:
@@ -185,7 +187,7 @@ async def wait_for_load_state(
185187
raise Error("state: expected one of (load|domcontentloaded|networkidle)")
186188
if state in self._load_states:
187189
return
188-
wait_helper = self._setup_navigation_wait_helper(timeout)
190+
wait_helper = self._setup_navigation_wait_helper("wait_for_load_state", timeout)
189191
wait_helper.wait_for_event(
190192
self._event_emitter, "loadstate", lambda s: s == state
191193
)

playwright/_impl/_logger.py

Lines changed: 0 additions & 32 deletions
This file was deleted.

playwright/_impl/_network.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -301,7 +301,7 @@ def expect_event(
301301
) -> EventContextManagerImpl:
302302
if timeout is None:
303303
timeout = cast(Any, self._parent)._timeout_settings.timeout()
304-
wait_helper = WaitHelper(self._loop)
304+
wait_helper = WaitHelper(self, f"expect_event({event})")
305305
wait_helper.reject_on_timeout(
306306
timeout, f'Timeout while waiting for event "{event}"'
307307
)

playwright/_impl/_page.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -779,7 +779,7 @@ def expect_event(
779779
) -> EventContextManagerImpl:
780780
if timeout is None:
781781
timeout = self._timeout_settings.timeout()
782-
wait_helper = WaitHelper(self._loop)
782+
wait_helper = WaitHelper(self, f"expect_event({event})")
783783
wait_helper.reject_on_timeout(
784784
timeout, f'Timeout while waiting for event "{event}"'
785785
)

playwright/_impl/_sync_base.py

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

1515
import asyncio
16-
from typing import Any, Callable, Dict, Generic, List, Optional, TypeVar, cast
16+
import traceback
17+
from typing import (
18+
Any,
19+
Awaitable,
20+
Callable,
21+
Dict,
22+
Generic,
23+
List,
24+
Optional,
25+
TypeVar,
26+
cast,
27+
)
1728

1829
import greenlet
1930

@@ -70,24 +81,26 @@ def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
7081
class SyncBase(ImplWrapper):
7182
def __init__(self, impl_obj: Any) -> None:
7283
super().__init__(impl_obj)
73-
self._loop = impl_obj._loop
84+
self._loop: asyncio.BaseEventLoop = impl_obj._loop
7485
self._dispatcher_fiber = impl_obj._dispatcher_fiber
7586

7687
def __str__(self) -> str:
7788
return self._impl_obj.__str__()
7889

79-
def _sync(self, task: asyncio.Future) -> Any:
90+
def _sync(self, coro: Awaitable) -> Any:
91+
stack_trace = traceback.extract_stack()
8092
g_self = greenlet.getcurrent()
81-
future = self._loop.create_task(task)
93+
task = self._loop.create_task(coro)
94+
setattr(task, "__pw_stack_trace__", stack_trace)
8295

8396
def callback(result: Any) -> None:
8497
g_self.switch()
8598

86-
future.add_done_callback(callback)
87-
while not future.done():
99+
task.add_done_callback(callback)
100+
while not task.done():
88101
self._dispatcher_fiber.switch()
89102
asyncio._set_running_loop(self._loop)
90-
return future.result()
103+
return task.result()
91104

92105
def _wrap_handler(self, handler: Any) -> Callable[..., None]:
93106
if callable(handler):

playwright/_impl/_wait_helper.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,22 +13,27 @@
1313
# limitations under the License.
1414

1515
import asyncio
16+
import uuid
1617
from asyncio.tasks import Task
1718
from typing import Any, Callable, Generic, List, Tuple, TypeVar
1819

1920
from pyee import EventEmitter
2021

2122
from playwright._impl._api_types import Error, TimeoutError
23+
from playwright._impl._connection import ChannelOwner
2224

2325
T = TypeVar("T")
2426

2527

2628
class WaitHelper(Generic[T]):
27-
def __init__(self, loop: asyncio.AbstractEventLoop) -> None:
29+
def __init__(self, channel_owner: ChannelOwner, name: str) -> None:
2830
self._result: asyncio.Future = asyncio.Future()
29-
self._loop = loop
31+
self._wait_id = uuid.uuid4().hex
32+
self._loop = channel_owner._loop
3033
self._pending_tasks: List[Task] = []
34+
self._channel_owner = channel_owner
3135
self._registered_listeners: List[Tuple[EventEmitter, str, Callable]] = []
36+
channel_owner._wait_for_event_info_before(self._wait_id, name)
3237

3338
def reject_on_event(
3439
self,
@@ -65,11 +70,13 @@ def _fulfill(self, result: Any) -> None:
6570
self._cleanup()
6671
if not self._result.done():
6772
self._result.set_result(result)
73+
self._channel_owner._wait_for_event_info_after(self._wait_id)
6874

6975
def _reject(self, exception: Exception) -> None:
7076
self._cleanup()
7177
if not self._result.done():
7278
self._result.set_exception(exception)
79+
self._channel_owner._wait_for_event_info_after(self._wait_id, exception)
7380

7481
def wait_for_event(
7582
self,

0 commit comments

Comments
 (0)