Skip to content

Commit

Permalink
fix(tracing): apiName calculation for route handlers (microsoft#1409)
Browse files Browse the repository at this point in the history
test: clean up tracing tests
  • Loading branch information
mxschmitt committed Jul 6, 2022
1 parent 5b3cb55 commit 8820f30
Show file tree
Hide file tree
Showing 5 changed files with 78 additions and 30 deletions.
44 changes: 29 additions & 15 deletions playwright/_impl/_connection.py
Expand Up @@ -50,7 +50,7 @@ async def send_return_as_dict(self, method: str, params: Dict = None) -> Any:
)

def send_no_reply(self, method: str, params: Dict = None) -> None:
self._connection.wrap_api_call(
self._connection.wrap_api_call_sync(
lambda: self._connection._send_message_to_server(
self._guid, method, {} if params is None else params
)
Expand Down Expand Up @@ -355,26 +355,35 @@ def _replace_guids_with_channels(self, payload: Any) -> Any:
return result
return payload

def wrap_api_call(self, cb: Callable[[], Any], is_internal: bool = False) -> Any:
async def wrap_api_call(
self, cb: Callable[[], Any], is_internal: bool = False
) -> Any:
if self._api_zone.get():
return cb()
return await cb()
task = asyncio.current_task(self._loop)
st: List[inspect.FrameInfo] = getattr(task, "__pw_stack__", inspect.stack())
metadata = _extract_metadata_from_stack(st, is_internal)
if metadata:
self._api_zone.set(metadata)
result = cb()

async def _() -> None:
try:
return await result
finally:
self._api_zone.set(None)
try:
return await cb()
finally:
self._api_zone.set(None)

if asyncio.iscoroutine(result):
return _()
self._api_zone.set(None)
return result
def wrap_api_call_sync(
self, cb: Callable[[], Any], is_internal: bool = False
) -> Any:
if self._api_zone.get():
return cb()
task = asyncio.current_task(self._loop)
st: List[inspect.FrameInfo] = getattr(task, "__pw_stack__", inspect.stack())
metadata = _extract_metadata_from_stack(st, is_internal)
if metadata:
self._api_zone.set(metadata)
try:
return cb()
finally:
self._api_zone.set(None)


def from_channel(channel: Channel) -> Any:
Expand All @@ -388,6 +397,12 @@ def from_nullable_channel(channel: Optional[Channel]) -> Optional[Any]:
def _extract_metadata_from_stack(
st: List[inspect.FrameInfo], is_internal: bool
) -> Optional[Dict]:
if is_internal:
return {
"apiName": "",
"stack": [],
"internal": True,
}
playwright_module_path = str(Path(playwright.__file__).parents[0])
last_internal_api_name = ""
api_name = ""
Expand Down Expand Up @@ -419,6 +434,5 @@ def _extract_metadata_from_stack(
return {
"apiName": api_name,
"stack": stack,
"isInternal": is_internal,
}
return None
22 changes: 17 additions & 5 deletions playwright/_impl/_network.py
Expand Up @@ -14,6 +14,7 @@

import asyncio
import base64
import inspect
import json
import mimetypes
from collections import defaultdict
Expand Down Expand Up @@ -343,11 +344,14 @@ async def continue_route() -> None:
params["headers"] = serialize_headers(params["headers"])
if post_data_for_wire is not None:
params["postData"] = post_data_for_wire
await self._race_with_page_close(
self._channel.send(
"continue",
params,
)
await self._connection.wrap_api_call(
lambda: self._race_with_page_close(
self._channel.send(
"continue",
params,
)
),
is_internal,
)
except Exception as e:
if not is_internal:
Expand All @@ -369,6 +373,14 @@ async def _race_with_page_close(self, future: Coroutine) -> None:
# Note that page could be missing when routing popup's initial request that
# does not have a Page initialized just yet.
fut = asyncio.create_task(future)
# Rewrite the user's stack to the new task which runs in the background.
setattr(
fut,
"__pw_stack__",
getattr(
asyncio.current_task(self._loop), "__pw_stack__", inspect.stack()
),
)
await asyncio.wait(
[fut, page._closed_or_crashed_future],
return_when=asyncio.FIRST_COMPLETED,
Expand Down
4 changes: 2 additions & 2 deletions playwright/_impl/_wait_helper.py
Expand Up @@ -48,7 +48,7 @@ def _wait_for_event_info_before(self, wait_id: str, event: str) -> None:
)

def _wait_for_event_info_after(self, wait_id: str, error: Exception = None) -> None:
self._channel._connection.wrap_api_call(
self._channel._connection.wrap_api_call_sync(
lambda: self._channel.send_no_reply(
"waitForEventInfo",
{
Expand Down Expand Up @@ -127,7 +127,7 @@ def result(self) -> asyncio.Future:
def log(self, message: str) -> None:
self._logs.append(message)
try:
self._channel._connection.wrap_api_call(
self._channel._connection.wrap_api_call_sync(
lambda: self._channel.send_no_reply(
"waitForEventInfo",
{
Expand Down
19 changes: 15 additions & 4 deletions tests/async/test_tracing.py
Expand Up @@ -89,8 +89,13 @@ async def test_should_collect_trace_with_resources_but_no_js(
await page.mouse.dblclick(30, 30)
await page.keyboard.insert_text("abc")
await page.wait_for_timeout(2000) # Give it some time to produce screenshots.
await page.route("**/empty.html", lambda route: route.continue_())
await page.route(
"**/empty.html", lambda route: route.continue_()
) # should produce a route.continue_ entry.
await page.goto(server.EMPTY_PAGE)
await page.goto(
server.PREFIX + "/one-style.html"
) # should not produce a route.continue_ entry since we continue all routes if no match.
await page.close()
trace_file_path = tmpdir / "trace.zip"
await context.tracing.stop(path=trace_file_path)
Expand All @@ -107,8 +112,8 @@ async def test_should_collect_trace_with_resources_but_no_js(
"Page.wait_for_timeout",
"Page.route",
"Page.goto",
# FIXME: https://github.com/microsoft/playwright-python/issues/1397
"Channel.send",
"Route.continue_",
"Page.goto",
"Page.close",
"Tracing.stop",
]
Expand Down Expand Up @@ -215,7 +220,13 @@ def parse_trace(path: Path) -> Tuple[Dict[str, bytes], List[Any]]:

def get_actions(events: List[Any]) -> List[str]:
action_events = sorted(
list(filter(lambda e: e["type"] == "action", events)),
list(
filter(
lambda e: e["type"] == "action"
and e["metadata"].get("internal", False) is False,
events,
)
),
key=lambda e: e["metadata"]["startTime"],
)
return [e["metadata"]["apiName"] for e in action_events]
19 changes: 15 additions & 4 deletions tests/sync/test_tracing.py
Expand Up @@ -89,8 +89,13 @@ def test_should_collect_trace_with_resources_but_no_js(
page.mouse.dblclick(30, 30)
page.keyboard.insert_text("abc")
page.wait_for_timeout(2000) # Give it some time to produce screenshots.
page.route("**/empty.html", lambda route: route.continue_())
page.route(
"**/empty.html", lambda route: route.continue_()
) # should produce a route.continue_ entry.
page.goto(server.EMPTY_PAGE)
page.goto(
server.PREFIX + "/one-style.html"
) # should not produce a route.continue_ entry since we continue all routes if no match.
page.close()
trace_file_path = tmpdir / "trace.zip"
context.tracing.stop(path=trace_file_path)
Expand All @@ -107,8 +112,8 @@ def test_should_collect_trace_with_resources_but_no_js(
"Page.wait_for_timeout",
"Page.route",
"Page.goto",
# FIXME: https://github.com/microsoft/playwright-python/issues/1397
"Channel.send",
"Route.continue_",
"Page.goto",
"Page.close",
"Tracing.stop",
]
Expand Down Expand Up @@ -215,7 +220,13 @@ def parse_trace(path: Path) -> Tuple[Dict[str, bytes], List[Any]]:

def get_actions(events: List[Any]) -> List[str]:
action_events = sorted(
list(filter(lambda e: e["type"] == "action", events)),
list(
filter(
lambda e: e["type"] == "action"
and e["metadata"].get("internal", False) is False,
events,
)
),
key=lambda e: e["metadata"]["startTime"],
)
return [e["metadata"]["apiName"] for e in action_events]

0 comments on commit 8820f30

Please sign in to comment.